一:背景
上一篇我們聊到了C++ 的 ?auto_ptr
,有朋友說已經在 C++ 17
中被棄用了,感謝朋友提醒,今天我們來聊一下 C++ 11
中引入的幾個智能指針。
unique_ptr
shared_ptr
weak_ptr
看看它們都怎么玩。
二:三大智能指針詳解
1. unique_ptr
上一篇在說 auto_ptr
的時候聊過一個術語叫 控制權轉移
,言外之意就是下面的代碼會 訪問違例 。
void?test()?{int*?i?=?new?int(10);auto_ptr<int>?ptr1(i);auto_ptr<int>?ptr2?=?ptr1;??//將?ptr1?的控制臺送給了?ptr2cout?<<?*ptr1?<<?endl;
}int?main()?{test();
}
這段代碼用了賦值運算符,可能是程序員的無心之舉,卻引來程序的崩潰,那能不能規避下這種 無心
的行為呢? 當然是可以的,屏蔽掉 auto_ptr
中的賦值運算符不就可以了哈,C++ 中的 unique_ptr
就是這么實現的,可以看下源碼。
class?unique_ptr?{public:unique_ptr(const?unique_ptr&)?=?delete;unique_ptr&?operator=(const?unique_ptr&)?=?delete;
};
可以看到,unique_ptr 將 =,復制構造函數
都給屏蔽了,所以再硬寫的話,必然是報錯。

2. shared_ptr
這個算是純正的 引用計數
,高級語言中的 引用跟蹤
經常會拿它做比較,說它存在循環引用等各種問題。
在循環引用之前,先說下它的簡單用法。
void?test()?{int*?i?=?new?int(10);shared_ptr<int>?ptr1(i);shared_ptr<int>?ptr2?=?ptr1;shared_ptr<int>?ptr3?=?ptr2;printf("ptr.refcnt=?%d,?ptr2.refcnt=%d,?ptr3.refcnt=%d?\n",?ptr1.use_count(),?ptr2.use_count(),?ptr3.use_count());
}int?main()?{test();
}

再看下源碼,大概在內部共同維護了對 _Ref_count_base
引用。
class?_Ref_count_base?{
private:_Atomic_counter_t?_Uses?=?1;_Atomic_counter_t?_Weaks?=?1;
}
很顯然當所有的析構函數將 _Uses
降為 0 的時候就會對 i
進行釋放,參考源碼如下:
class?shared_ptr?:?public?_Ptr_base<_Ty>?{~shared_ptr()?noexcept?{?//?release?resourcethis->_Decref();}
}class?_Ref_count_base?{void?_Decref()?noexcept?{?//?decrement?use?countif?(_MT_DECR(_Uses)?==?0)?{_Destroy();_Decwref();}}
}
原理大概摸清楚了,接下來看下循環引用,它最大的問題就是讓 _Uses
永遠也不會為 0,
#include?<iostream>
#include?<string>using?namespace?std;class?BClass;class?AClass
{
public:int?i?=?10;shared_ptr<BClass>?b;
};class?BClass
{
public:int?i?=?11;shared_ptr<AClass>?a;
};void?test()?{AClass*?a?=?new?AClass();BClass*?b?=?new?BClass();shared_ptr<AClass>?aclass(a);shared_ptr<BClass>?bclass(b);aclass->b?=?bclass;bclass->a?=?aclass;printf("a.refcnt=%d,?b.refcnt=%d,?a=%x,b=%x?\n",?aclass.use_count(),?bclass.use_count(),?&(*a),?&(*b));
}int?main()?{test();
}

接下來可以在 main 方法結束的地方觀察 a,b
所有的 heap 塊的內存內容是否被釋放? 可以清楚的看到還在那里。。. 如下圖所示:

這就是 循環引用
造成的問題,這時候就需要用到 weak_ptr
了。
3. weak_ptr
弱引用就是它的引用不改變對原對象中 _Uses
的引用,接下來將 AClass 和 BClass 中的 shared_ptr
改成 weak_ptr
版。
class?AClass
{
public:int?i?=?10;weak_ptr<BClass>?b;
};class?BClass
{
public:int?i?=?11;weak_ptr<AClass>?a;
};

從圖中可以看到,此時 refcnt=1
, 再觀察下地址 0x007f71a8
的內容,可以發現已經被釋放啦。

好了,今天我們就聊這么多,希望對大家有幫助。