版權(quán)聲明:本文為博主原創(chuàng)文章,轉(zhuǎn)載請注明出處。更多精彩文章請關(guān)注微信公眾號:寫代碼的蘇東坡 https://blog.csdn.net/shihengzhen101/article/details/82286999
前言
weak_ptr
這個指針天生一副“小弟”的模樣,也是在C++11的時候引入的標準庫,它的出現(xiàn)完全是為了彌補它老大shared_ptr
天生有缺陷的問題,其實相比于上一代的智能指針auto_ptr
來說,新進老大shared_ptr
可以說近乎完美,但是通過引用計數(shù)實現(xiàn)的它,雖然解決了指針獨占的問題,但也引來了引用成環(huán)的問題,這種問題靠它自己是沒辦法解決的,所以在C++11的時候?qū)?code>shared_ptr和weak_ptr
一起引入了標準庫,用來解決循環(huán)引用的問題。
weak_ptr
本身也是一個模板類,但是不能直接用它來定義一個智能指針的對象,只能配合shared_ptr
來使用,可以將shared_ptr
的對象賦值給weak_ptr
,并且這樣并不會改變引用計數(shù)的值。查看weak_ptr
的代碼時發(fā)現(xiàn),它主要有lock
、swap
、reset
、expired
、operator=
、use_count
幾個函數(shù),與shared_ptr
相比多了lock
、expired
函數(shù),但是卻少了get
函數(shù),甚至連operator*
和 operator->
都沒有,可用的函數(shù)數(shù)量少的可憐,下面通過一些例子來了解一下weak_ptr
的具體用法。
使用環(huán)境
- VS2015 + Windows7(應(yīng)該是C++11標準)
- 頭文件
#include <memory>
- 命名空間
using namespace std;
測試過程
weak_ptr
解決shared_ptr
循環(huán)引用的問題
定義兩個類,每個類中又包含一個指向?qū)Ψ筋愋偷闹悄苤羔樧鳛槌蓡T變量,然后創(chuàng)建對象,設(shè)置完成后查看引用計數(shù)后退出,看一下測試結(jié)果:
class CB;
class CA
{
public:
CA() { cout << "CA() called! " << endl; }
~CA() { cout << "~CA() called! " << endl; }
void set_ptr(shared_ptr<CB>& ptr) { m_ptr_b = ptr; }
void b_use_count() { cout << "b use count : " << m_ptr_b.use_count() << endl; }
void show() { cout << "this is class CA!" << endl; }
private:
shared_ptr<CB> m_ptr_b;
};
class CB
{
public:
CB() { cout << "CB() called! " << endl; }
~CB() { cout << "~CB() called! " << endl; }
void set_ptr(shared_ptr<CA>& ptr) { m_ptr_a = ptr; }
void a_use_count() { cout << "a use count : " << m_ptr_a.use_count() << endl; }
void show() { cout << "this is class CB!" << endl; }
private:
shared_ptr<CA> m_ptr_a;
};
void test_refer_to_each_other()
{
shared_ptr<CA> ptr_a(new CA());
shared_ptr<CB> ptr_b(new CB());
cout << "a use count : " << ptr_a.use_count() << endl;
cout << "b use count : " << ptr_b.use_count() << endl;
ptr_a->set_ptr(ptr_b);
ptr_b->set_ptr(ptr_a);
cout << "a use count : " << ptr_a.use_count() << endl;
cout << "b use count : " << ptr_b.use_count() << endl;
}
測試結(jié)果如下:
CA() called!
CB() called!
a use count : 1
b use count : 1
a use count : 2
b use count : 2
通過結(jié)果可以看到,最后CA
和CB
的對象并沒有被析構(gòu),其中的引用效果如下圖所示,起初定義完ptr_a
和ptr_b
時,只有①③兩條引用,然后調(diào)用函數(shù)set_ptr
后又增加了②④兩條引用,當test_refer_to_each_other
這個函數(shù)返回時,對象ptr_a
和ptr_b
被銷毀,也就是①③兩條引用會被斷開,但是②④兩條引用依然存在,每一個的引用計數(shù)都不為0,結(jié)果就導(dǎo)致其指向的內(nèi)部對象無法析構(gòu),造成內(nèi)存泄漏。
解決這種狀況的辦法就是將兩個類中的一個成員變量改為
weak_ptr
對象,因為
weak_ptr
不會增加引用計數(shù),使得引用形不成環(huán),最后就可以正常的釋放內(nèi)部的對象,不會造成內(nèi)存泄漏,比如將
CB
中的成員變量改為
weak_ptr
對象,代碼如下:
class CB
{
public:
CB() { cout << "CB() called! " << endl; }
~CB() { cout << "~CB() called! " << endl; }
void set_ptr(shared_ptr<CA>& ptr) { m_ptr_a = ptr; }
void a_use_count() { cout << "a use count : " << m_ptr_a.use_count() << endl; }
void show() { cout << "this is class CB!" << endl; }
private:
weak_ptr<CA> m_ptr_a;
};
測試結(jié)果如下:
CA() called!
CB() called!
a use count : 1
b use count : 1
a use count : 1
b use count : 2
~CA() called!
~CB() called!
通過這次結(jié)果可以看到,CA
和CB
的對象都被正常的析構(gòu)了,引用關(guān)系如下圖所示,流程與上一例子相似,但是不同的是④這條引用是通過weak_ptr
建立的,并不會增加引用計數(shù),也就是說CA
的對象只有一個引用計數(shù),而CB
的對象只有2個引用計數(shù),當test_refer_to_each_other
這個函數(shù)返回時,對象ptr_a
和ptr_b
被銷毀,也就是①③兩條引用會被斷開,此時CA
對象的引用計數(shù)會減為0,對象被銷毀,其內(nèi)部的m_ptr_b
成員變量也會被析構(gòu),導(dǎo)致CB
對象的引用計數(shù)會減為0,對象被銷毀,進而解決了引用成環(huán)的問題。
- 測試
weak_ptr
對引用計數(shù)的影響
其實weak_ptr
本身設(shè)計的很簡單,就是為了輔助shared_ptr
的,它本身不能直接定義指向原始指針的對象,只能指向shared_ptr
對象,同時也不能將weak_ptr對象直接賦值給shared_ptr
類型的變量,最重要的一點是賦值給它不會增加引用計數(shù):
void test1()
{
// 編譯錯誤 // error C2665: “std::weak_ptr<CA>::weak_ptr”: 3 個重載中沒有一個可以轉(zhuǎn)換所有參數(shù)類型
// weak_ptr<CA> ptr_1(new CA());
shared_ptr<CA> ptr_1(new CA());
cout << "ptr_1 use count : " << ptr_1.use_count() << endl; // 輸出:ptr_1 use count : 1
shared_ptr<CA> ptr_2 = ptr_1;
cout << "ptr_1 use count : " << ptr_1.use_count() << endl; // 輸出:ptr_1 use count : 2
cout << "ptr_2 use count : " << ptr_2.use_count() << endl; // 輸出:ptr_1 use count : 2
weak_ptr<CA> wk_ptr = ptr_1;
cout << "ptr_1 use count : " << ptr_1.use_count() << endl; // 輸出:ptr_1 use count : 2
cout << "ptr_2 use count : " << ptr_2.use_count() << endl; // 輸出:ptr_1 use count : 2
// 編譯錯誤
// error C2440 : “初始化”: 無法從“std::weak_ptr<CA>”轉(zhuǎn)換為“std::shared_ptr<CA>”
// shared_ptr<CA> ptr_3 = wk_ptr;
}
- 測試
weak_ptr
常用函數(shù)的用法
weak_ptr
中只有函數(shù)lock
和expired
兩個函數(shù)比較重要,因為它本身不會增加引用計數(shù),所以它指向的對象可能在它用的時候已經(jīng)被釋放了,所以在用之前需要使用expired
函數(shù)來檢測是否過期,然后使用lock
函數(shù)來獲取其對應(yīng)的shared_ptr
對象,然后進行后續(xù)操作:
void test2()
{
shared_ptr<CA> ptr_a(new CA()); // 輸出:CA() called!
shared_ptr<CB> ptr_b(new CB()); // 輸出:CB() called!
cout << "ptr_a use count : " << ptr_a.use_count() << endl; // 輸出:ptr_a use count : 1
cout << "ptr_b use count : " << ptr_b.use_count() << endl; // 輸出:ptr_b use count : 1
weak_ptr<CA> wk_ptr_a = ptr_a;
weak_ptr<CB> wk_ptr_b = ptr_b;
if (!wk_ptr_a.expired())
{
wk_ptr_a.lock()->show(); // 輸出:this is class CA!
}
if (!wk_ptr_b.expired())
{
wk_ptr_b.lock()->show(); // 輸出:this is class CB!
}
// 編譯錯誤
// 編譯必須作用于相同的指針類型之間
// wk_ptr_a.swap(wk_ptr_b); // 調(diào)用交換函數(shù)
wk_ptr_b.reset(); // 將wk_ptr_b的指向清空
if (wk_ptr_b.expired())
{
cout << "wk_ptr_b is invalid" << endl; // 輸出:wk_ptr_b is invalid 說明改指針已經(jīng)無效
}
wk_ptr_b = ptr_b;
if (!wk_ptr_b.expired())
{
wk_ptr_b.lock()->show(); // 輸出:this is class CB! 調(diào)用賦值操作后,wk_ptr_b恢復(fù)有效
}
// 編譯錯誤
// 編譯必須作用于相同的指針類型之間
// wk_ptr_b = wk_ptr_a;
// 最后輸出的引用計數(shù)還是1,說明之前使用weak_ptr類型賦值,不會影響引用計數(shù)
cout << "ptr_a use count : " << ptr_a.use_count() << endl; // 輸出:ptr_a use count : 1
cout << "ptr_b use count : " << ptr_b.use_count() << endl; // 輸出:ptr_b use count : 1
}
現(xiàn)象分析
引用計數(shù)的出現(xiàn),解決了對象獨占的問題,但是也帶來了循環(huán)引用的困擾,使用weak_ptr
可以打破這種循環(huán),當你理不清引用關(guān)系的時候,不妨采用文中畫圖的方式來理一理頭緒,或許就會有眼前一亮的感覺。
總結(jié)
weak_ptr
雖然是一個模板類,但是不能用來直接定義指向原始指針的對象。
weak_ptr
接受shared_ptr
類型的變量賦值,但是反過來是行不通的,需要使用lock
函數(shù)。
weak_ptr
設(shè)計之初就是為了服務(wù)于shared_ptr
的,所以不增加引用計數(shù)就是它的核心功能。
- 由于不知道什么之后
weak_ptr
所指向的對象就會被析構(gòu)掉,所以使用之前請先使用expired
函數(shù)檢測一下。
測試源碼
示例傳送門:weak_ptr用法