C++11新特性:shared_ptr與weak_ptr(實例測試)
std::shared_ptr
std::shared_ptr是通過指針保持對象共享所有權的智能指針,多個std::shared_ptr引用對象佔用同一個對象。當指針對象被另一個對象引用時可以把所有權共享給另一個引用指針對象,被管理的指針是當use_count抵達0是被刪除。在下列情況之一出現時銷燬對象並釋放內存:
最後佔有std::shared_ptr對象被銷燬時;
最後佔有std::shared_ptr對象被通過operator=或reset()賦值為另一個指針。
智能指針的本質
C++11在std::auto_ptr基礎上新增了std::shared_ptr、std::weak_ptr智能指針,同時也修復了boot::make_ptr構造參數限制問題(boost::make_shared()不能接受任意多數量的參數來構造對象)。其實智能指針也並沒有像它名字聽起來那麼神奇,它只是用對象去管理了一個資源指針,同時用一個計數器去計算當前指針引用對象的個數;當管理指針的對象增加或者減少時,計算器當前值也同步加1或減1,當最後一個指針對象被銷燬時,計算器為1;此時,最後的指針對象被銷燬(計算器抵達0)的同時也把指針管理對象的指針進行delete操作。
通過例子說明以上操作:
#include<iostream>
#include <memory>
class Test
{
public:
Test(){
std::cout << "Test()" << std::endl;
}
~Test(){
std::cout << "~Test()" << std::endl;
}
};
int main(){
std::shared_ptr<test> p1 = std::make_shared<test>();/<test>/<test>
std::cout << "1 ref:" << p1.use_count() << std::endl;
{
std::shared_ptr<test> p2 = p1;/<test>
std::cout << "2 ref:" << p1.use_count() << std::endl;
std::shared_ptr<test> p3 = p1;/<test>
std::cout << "3 ref:" << p1.use_count() << std::endl;
}
std::cout << "4 ref:" << p1.use_count() << std::endl;
system("pause");
return 0;
}
輸出結果:
std::make_shared封裝了new操作符,實現了對象的實例化。剛開始引用對象只有一個,因此打印1。當執行std::shared_ptr<test> p2 = p1和std::shared_ptr<test> p3 = p1,引用對象各自加1,一次打印2和3,當大括號結束,p2和p3生命週期結束,計算器執行兩次減1操作,p1引用計算重新回到1,當函數運行完之時,p1會被銷燬,此時計算器是1,就會調用p1的析構函數delete之前用std:: make_shared創建的指針。完成整個引用對象的管理。/<test>/<test>
std::shared_ptr解決了對象共享所有權的問題,那麼它是不是完美的,有沒有其他問題存在,答案是肯定的,如當兩個類相互引用時就會產生對象釋放異常的問題。
請看實例:
#include <iostream>
#include <memory>
class B;
class A{
public:
A(){
std::cout << "class A : constructor" << std::endl;
}
~A(){
std::cout << "class A : destructor" << std::endl;
}
void referB(std::shared_ptr test_ptr) {
_B_Ptr = test_ptr;
}
private:
std::shared_ptr _B_Ptr;
};
class B{
public:
B(){
std::cout << "class B : constructor" << std::endl;
}
~B() {
std::cout << "class B : destructor" << std::endl;
}
void referA(std::shared_ptr
_A_Ptr = test_ptr;
}
std::shared_ptr
};
int main()
{
// test
{
std::shared_ptr
std::shared_ptr ptr_b = std::make_shared(); //B引用計算器為1
ptr_a->referB(ptr_b); // B引用計算器加1
ptr_b->referA(ptr_a); // A引用計算器加1
}
system("pause");
return 0;
}
運行結果:
從輸出結果可以看到,整個程序運行結束都沒有看到A和B調用析構函數,這是怎麼回事呢?
分析上面代碼可知,std::shared_ptr
ptr_a等待ptr_b釋放自己,這樣ptr_a才能去釋放ptr_b。同理,ptr_b也等待ptr_a釋放自己,這樣ptr_b才能去釋放ptr_a。
可見,相互引用導致了相互釋放衝突的問題,最終導致內存洩露發生。
那用什麼方法可以解決這個問題呢?
還有C++11同時提供了std::weak_ptr,利用std::weak_ptr就可以解決以上問題。
std::weak_ptr
#include <iostream>
#include <memory>
class B;
class A{
public:
A() {
std::cout << "class A : constructor" << std::endl;
}
~A() {
std::cout << "class A : destructor" << std::endl;
}
void referB(std::shared_ptr test_ptr) {
_B_Ptr = test_ptr;
}
void print_refer() {
std::cout << "refer A count : " << _B_Ptr.use_count() << std::endl;
}
void test_refer() {
std::shared_ptr tem_p = _B_Ptr.lock();
std::cout << "refer B : " << tem_p.use_count() << std::endl;
}
private:
std::weak_ptr _B_Ptr;
};
class B{
public:
B() {
std::cout << "class B : constructor" << std::endl;
}
~B() {
std::cout << "class B : destructor" << std::endl;
}
void referA(std::shared_ptr
{
_A_Ptr = test_ptr;
}
void print_refer() {
std::cout << "refer A count : " << _A_Ptr.use_count() << std::endl;
}
void test_refer() {
std::shared_ptr
std::cout << "refer A : " << tem_p.use_count() << std::endl;
}
std::weak_ptr
};
int main(){
// test
{
std::shared_ptr
std::shared_ptr ptr_b = std::make_shared(); //B引用計算器為1
ptr_a->referB(ptr_b);
ptr_b->referA(ptr_a);
ptr_a->test_refer();
ptr_b->test_refer();
ptr_a->print_refer();
ptr_b->print_refer();
}
system("pause");
return 0;
}
運行結果:
可以看到ptr_a和ptr_b在main退出前,引用計算均為1,也就是說在A和B中對std::weak_ptr的引用不會引起引用計算加1,ptr_a和ptr_b可以正常釋放,不會引起內存洩露。
可見std::weak_ptr擁有弱引用特性,不擁有對象,只有等到調用lock()函數時才會有可能擁有對象,std::weak_ptr有以下特性:
只是擁有一個沒有擁有的被std::shared_ptr託管的對象;
只有調用lock()創建std::shared_ptr時才會引用對象;
在lock()時會遞增一個引用計算;
在std::shared_ptr主指針結束後,如果std::weak_ptr的lock成功對象還存在,那麼此時還有代碼調用lock的話,也會引起引用計算加1。
閱讀更多 KobeBryand2020 的文章