shared_ptr循環(huán)引用是指兩個(gè)對象互相持有對方的shared_ptr,導(dǎo)致引用計(jì)數(shù)無法歸零而引發(fā)內(nèi)存泄漏。例如,結(jié)構(gòu)體a持有b的shared_ptr,b也持有a的shared_ptr,當(dāng)外部不再引用它們時(shí),內(nèi)部引用仍保持計(jì)數(shù),阻止釋放。解決方法是使用weak_ptr替代其中一個(gè)shared_ptr,如將b中的a_ptr改為weak_ptr,則不會(huì)增加a的引用計(jì)數(shù),從而打破循環(huán)。使用weak_ptr時(shí)需注意:1.訪問前必須調(diào)用lock()獲取shared_ptr;2.需判斷l(xiāng)ock()返回是否為空;3.適用于觀察者等無需強(qiáng)引用的場景。常見易發(fā)循環(huán)引用的場景包括:樹形結(jié)構(gòu)中父子節(jié)點(diǎn)互指、觀察者模式訂閱者與發(fā)布者互指、gui控件間互指等,這些都可通過改用weak_ptr避免內(nèi)存泄漏。
shared_ptr 的循環(huán)引用會(huì)導(dǎo)致內(nèi)存泄漏,這是使用智能指針管理資源時(shí)常見的一個(gè)坑。簡單來說,當(dāng)兩個(gè)對象互相持有對方的 shared_ptr 時(shí),它們的引用計(jì)數(shù)永遠(yuǎn)無法降為0,于是誰都不會(huì)被釋放,造成內(nèi)存泄露。
什么是 shared_ptr 循環(huán)引用?
循環(huán)引用通常發(fā)生在兩個(gè)對象彼此持有對方的 shared_ptr。例如:
struct B; struct A { std::shared_ptr<B> b_ptr; }; struct B { std::shared_ptr<A> a_ptr; };
當(dāng)你創(chuàng)建兩個(gè)對象并讓它們互相引用時(shí):
auto a = std::make_shared<A>(); auto b = std::make_shared<B>(); a->b_ptr = b; b->a_ptr = a;
此時(shí),a 和 b 的引用計(jì)數(shù)都為1(各自外部持有的那個(gè)),而它們內(nèi)部又各持有一個(gè)對方的 shared_ptr。所以即使你不再從外部訪問這兩個(gè)對象,它們的引用計(jì)數(shù)也始終是1,導(dǎo)致無法釋放。
如何用 weak_ptr 解決這個(gè)問題?
weak_ptr 是一種不增加引用計(jì)數(shù)的智能指針,它用來觀察 shared_ptr 所管理的對象。它不會(huì)影響對象的生命周期,只在需要時(shí)臨時(shí)升級為 shared_ptr 使用。
要打破循環(huán)引用,只需要把其中一個(gè)引用改為 weak_ptr:
struct A { std::shared_ptr<B> b_ptr; }; struct B { std::weak_ptr<A> a_ptr; // 改成 weak_ptr };
這樣,在構(gòu)建循環(huán)鏈路時(shí),只有 A 持有 B 的強(qiáng)引用,而 B 對 A 的引用是弱引用,不會(huì)阻止 A 被釋放。
使用 weak_ptr 的注意事項(xiàng)
雖然 weak_ptr 可以避免循環(huán)引用,但使用時(shí)也要注意以下幾點(diǎn):
-
訪問前必須 lock():weak_ptr 不能直接使用,必須調(diào)用 lock() 方法獲取一個(gè) shared_ptr 實(shí)例。
if (auto a = b->a_ptr.lock()) { // 安全使用 a } else { // 對象已經(jīng)被釋放 }
-
可能為空:因?yàn)?weak_ptr 不控制生命周期,所以在使用時(shí)要判斷對象是否還存在。
-
適合“觀察者”場景:比如緩存、回調(diào)注冊、事件監(jiān)聽等不需要強(qiáng)引用的場合。
哪些場景容易出現(xiàn)循環(huán)引用?
除了上面的例子,還有一些常見情況容易出問題:
- 樹形結(jié)構(gòu)中父子節(jié)點(diǎn)互相引用(如父節(jié)點(diǎn)持有子節(jié)點(diǎn)的 shared_ptr,子節(jié)點(diǎn)反過來持有父節(jié)點(diǎn)的 shared_ptr)
- 觀察者模式中,訂閱者和發(fā)布者之間互相持有指針
- GUI 中控件之間的相互引用(比如按鈕和窗口)
這些場景都可以考慮將“被動(dòng)方”的引用改為 weak_ptr 來打破循環(huán)。
基本上就這些。用 weak_ptr 替代不必要的 shared_ptr 是個(gè)好習(xí)慣,尤其是在設(shè)計(jì)類之間的關(guān)系時(shí)提前考慮引用方向,能有效避免很多潛在的內(nèi)存泄漏問題。