文章目錄
- weak_ptr描述
- 聲明
- 作用
- 原理實作
- 函數成員使用
- 總結
weak_ptr描述
聲明
頭檔案:
<memory>
模版類:
template <class T> class weak_ptr
聲明方式:
std::weak_ptr<type_id> statement
作用
根據boost庫的官方描述,weak_ptr是由shared_ptr管理的一種弱引用對象的模版類。
weak_ptr
的對象能夠使用
shared_ptr
指針的構造函數轉換為一個shared_ptr對象。但是這裡
shared_ptr
的構造函數參數需要包含
weak_ptr
的lock成員,該成員是weak_ptr用來擷取shared_ptr的指針。
這樣當shared_ptr在多線程過程中被銷毀時
shared_ptr::reset
,weak_ptr的lock成員仍然能夠保留shared_ptr的成員,直到目前shared_ptr正常終止,否則會出現非常危險的記憶體洩漏。關于lock()成員的作用如下描述:
如下代碼
shared_ptr<int> p(new int(5));
weak_ptr<int> q(p);
// some time later
if(int * r = q.get())
{
// use *r
}
多線程環境中shared_ptr是可以被多個線程共享,在r被使用之前p對象執行了
p.reset()
,正如我們上一篇文章中對shared_ptr的描述(C++智能指針:shared_ptr 實作詳解),reset成員會重置目前shared_ptr指針的指向。此時,目前線程如果繼續使用r指針,勢必會産生通路空位址的異常問題
根據以上問題,使用
weak_ptr::lock()
成員來解決該問題
shared_ptr<int> p(new int(5));
weak_ptr<int> q(p);
// some time later
//使用weak_ptr的lock成員來擷取shared_ptr的指針
if(shared_ptr<int> r = q.lock())
{
// use *r
}
關于lock()成員簡單說明一下,lock成員擷取到的shared_ptr p指針建立一個臨時對象(我們weak_ptr弱引用的展現),這個臨時對象同樣指向p,即使p執了reset這樣的delete引用的操作,弱引用對象仍然持有改智能指針的位址,直到r指針的生命周期結束才會釋放。不得不佩服C++語言設計者的腦洞,為了保持C++對記憶體操作的自由,即使耗費再大的精力也要實作這一目标,工匠精神才讓今天的C++越來越被底層程式員喜歡。
原理實作
源碼檔案
/boost/smart_ptr/weak_ptr.hpp
template<class T> class weak_ptr
{
private:
// Borland 5.5.1 specific workarounds
typedef weak_ptr<T> this_type;
-
構造函數constructor
//預設構造函數
weak_ptr() BOOST_NOEXCEPT : px(0), pn() // never throws in 1.30+
{
}
//拷貝構造函數
weak_ptr( weak_ptr const & r ) BOOST_NOEXCEPT : px( r.px ), pn( r.pn )
{
}
-
析構函數,這裡weak_ptr使用的是預設析構函數,一般使用destructor
傳回空對象或者user_count()為0的情況則輔助shared_ptr釋放引用expired
-
operator=
1.
weak_ptr & operator=( weak_ptr && r ) BOOST_NOEXCEPT
{
this_type( static_cast< weak_ptr && >( r ) ).swap( *this );
return *this;
}
//2.這裡會更加安全,使用lock成員擷取Y類型的指針
template<class Y>
weak_ptr & operator=( weak_ptr<Y> const & r ) BOOST_NOEXCEPT
{
boost::detail::sp_assert_convertible< Y, T >();
px = r.lock().get();
pn = r.pn;
return *this;
}
3.
template<class Y>
weak_ptr & operator=( shared_ptr<Y> const & r ) BOOST_NOEXCEPT
{
boost::detail::sp_assert_convertible< Y, T >();
px = r.px;
pn = r.pn;
return *this;
}
-
成員,交換兩個weak_ptr所指内容以及位址weak_ptr::swap
void swap(this_type & other) BOOST_NOEXCEPT
{
//先交換位址,再交換内容
std::swap(px, other.px);
pn.swap(other.pn);
}
-
成員,·重新指定對象位址和内容,就像是重新使用預設構造函數進行了初始化weak_ptr::reset
void reset() BOOST_NOEXCEPT // never throws in 1.30+
{
//使用預設構造函數構造的對象和目前對象進行swap操作
this_type().swap(*this);
}
-
成員,擷取shared_ptr對象被引用的次數。如果為空,則傳回0weak_ptr::use_count
long use_count() const BOOST_NOEXCEPT
{
return pn.use_count();
}
-
成員,當根據use_count==0來傳回bool,其傳回為true的時候,使用lock擷取weak_ptr的指針隻能擷取到空指針weak_ptr::expired
bool expired() const BOOST_NOEXCEPT
{
return pn.use_count() == 0;
}
-
成員,會向weak_ptr對象傳回一個shared_ptr。正如我們之前在weak_ptr作用中所描述的,防止多線程通路時的shared_ptr記憶體洩漏。此時weak_ptr對象擷取到的指針為臨時指針,會指向shared_ptr對象之前所指向的位址。weak_ptr::lock
shared_ptr<T> lock() const BOOST_NOEXCEPT
{
return shared_ptr<T>( *this, boost::detail::sp_nothrow_tag() );
}
函數成員使用
- 構造函數
#include <iostream>
#include <memory>
struct C {int* data;};
int main () {
std::shared_ptr<int> sp (new int);
std::weak_ptr<int> wp1;
std::weak_ptr<int> wp2 (wp1);
std::weak_ptr<int> wp3 (sp);
std::cout << "use_count:\n";
//weak_ptr對象如果為經shared_ptr初始化,
//它是沒有引用計數的,是以這裡wp1和wp2引用計數都為0
//隻有wp3經過了shared_ptr初始化,它的引用計數才為1
std::cout << "wp1: " << wp1.use_count() << '\n';
std::cout << "wp2: " << wp2.use_count() << '\n';
std::cout << "wp3: " << wp3.use_count() << '\n';
return 0;
}
- 輸出如下:
use_count:
wp1: 0
wp2: 0
wp3: 1
-
指派運算符weak_ptr::operator=
// weak_ptr::operator= example
#include <iostream>
#include <memory>
int main () {
std::shared_ptr<int> sp1,sp2;
std::weak_ptr<int> wp;
// sharing group:
// --------------
sp1 = std::make_shared<int> (10); // sp1
wp = sp1; // sp1, wp
sp2 = wp.lock(); // sp1, wp, sp2
sp1.reset(); // wp, sp2
//通過lock保留的臨時指針,重新擷取到了shared_ptr共享的位址
sp1 = wp.lock(); // sp1, wp, sp2
std::cout << "*sp1: " << *sp1 << '\n';
std::cout << "*sp2: " << *sp2 << '\n';
return 0;
}
- 輸出如下
*sp1: 10
*sp2: 10
-
交換指針位址以及對應的内容std::weak_ptr::swap
#include <iostream>
#include <memory>
int main () {
std::shared_ptr<int> sp1 (new int(10));
std::shared_ptr<int> sp2 (new int(20));
std::weak_ptr<int> wp1(sp1);
std::weak_ptr<int> wp2(sp2);
std::cout << "wp1 -> " << *wp1.lock() << " " << wp1.lock() << '\n';
std::cout << "wp2 -> " << *wp2.lock() << " " << wp2.lock() << '\n';
//這裡的swap僅僅是交換wp2的各自的指向位址
//并不會直接導緻對應智能指針原始指針的位址交換
//根據輸出,是以很明顯,交換完成之後weak_ptr對象指向發生了變化,但是并未導緻share_ptr指針的指向變化
wp1.swap(wp2);
std::cout << "sp1 -> " << *sp1 << " " << sp1 << '\n';
std::cout << "sp2 -> " << *sp2 << " " << sp2 << '\n';
std::cout << "wp1 -> " << *wp1.lock() << " " << wp1.lock() << '\n';
std::cout << "wp2 -> " << *wp2.lock() << " " << wp2.lock() << '\n';
return 0;
}
- 輸出如下:
wp1 -> 10 0x11daf90
wp2 -> 20 0x11dafd0
sp1 -> 10 0x11daf90
sp2 -> 20 0x11dafd0
wp1 -> 20 0x11dafd0
wp2 -> 10 0x11daf90
-
成員,執行之後weak_ptr對象就像是重新執行了預設構造函數,又變成了一個空的對象std::weak_ptr::reset
// weak_ptr::reset example
#include <iostream>
#include <memory>
int main () {
std::shared_ptr<int> sp (new int(10));
std::weak_ptr<int> wp(sp);
std::cout << "1. wp " << (wp.expired()?"is":"is not") << " expired\n";
std::cout << "4. wp " << wp.use_count() << " *wp " << *wp.lock() << '\n';
wp.reset();
std::cout << "2. wp " << (wp.expired()?"is":"is not") << " expired\n";
std::cout << "3. sp " << sp.use_count() << " *sp " << *sp << '\n';
return 0;
}
- 輸出如下
1. wp is not expired
2. wp 2 *wp 10
3. wp is expired
4. sp 1 *sp 10
總結
- shared_ptr對象能夠初始化實際指向一個位址内容而weak_ptr對象沒辦法直接初始化一個具體位址,它的對象需要由shared_ptr去初始化
- weak_ptr不會影響shared_ptr的引用計數,因為它是一個弱引用,隻是一個臨時引用指向shared_ptr。即使用shared_ptr對象初始化weak_ptr不會導緻shared_ptr引用計數增加。依此特性可以解決shared_ptr的循環引用問題。
- weak_ptr沒有解引用*和擷取指針->運算符,它隻能通過lock成員函數去擷取對應的shared_ptr智能指針對象,進而擷取對應的位址和内容。