shared_ptr循環引用會導致什么問題 如何用weak_ptr避免內存泄漏

shared_ptr循環引用是指兩個對象互相持有對方的shared_ptr,導致引用計數無法歸零而引發內存泄漏。例如,結構體a持有b的shared_ptr,b也持有a的shared_ptr,當外部不再引用它們時,內部引用仍保持計數,阻止釋放。解決方法是使用weak_ptr替代其中一個shared_ptr,如將b中的a_ptr改為weak_ptr,則不會增加a的引用計數,從而打破循環。使用weak_ptr時需注意:1.訪問前必須調用lock()獲取shared_ptr;2.需判斷lock()返回是否為空;3.適用于觀察者等無需強引用的場景。常見易發循環引用的場景包括:樹形結構中父子節點互指、觀察者模式訂閱者與發布者互指、gui控件間互指等,這些都可通過改用weak_ptr避免內存泄漏。

shared_ptr循環引用會導致什么問題 如何用weak_ptr避免內存泄漏

shared_ptr 的循環引用會導致內存泄漏,這是使用智能指針管理資源時常見的一個坑。簡單來說,當兩個對象互相持有對方的 shared_ptr 時,它們的引用計數永遠無法降為0,于是誰都不會被釋放,造成內存泄露。

shared_ptr循環引用會導致什么問題 如何用weak_ptr避免內存泄漏


什么是 shared_ptr 循環引用?

循環引用通常發生在兩個對象彼此持有對方的 shared_ptr。例如:

shared_ptr循環引用會導致什么問題 如何用weak_ptr避免內存泄漏

struct B;  struct A {     std::shared_ptr<B> b_ptr; };  struct B {     std::shared_ptr<A> a_ptr; };

當你創建兩個對象并讓它們互相引用時:

auto a = std::make_shared<A>(); auto b = std::make_shared<B>(); a->b_ptr = b; b->a_ptr = a;

此時,a 和 b 的引用計數都為1(各自外部持有的那個),而它們內部又各持有一個對方的 shared_ptr。所以即使你不再從外部訪問這兩個對象,它們的引用計數也始終是1,導致無法釋放。

shared_ptr循環引用會導致什么問題 如何用weak_ptr避免內存泄漏


如何用 weak_ptr 解決這個問題?

weak_ptr 是一種不增加引用計數的智能指針,它用來觀察 shared_ptr 所管理的對象。它不會影響對象的生命周期,只在需要時臨時升級為 shared_ptr 使用。

要打破循環引用,只需要把其中一個引用改為 weak_ptr:

struct A {     std::shared_ptr<B> b_ptr; };  struct B {     std::weak_ptr<A> a_ptr; // 改成 weak_ptr };

這樣,在構建循環鏈路時,只有 A 持有 B 的強引用,而 B 對 A 的引用是弱引用,不會阻止 A 被釋放。


使用 weak_ptr 的注意事項

雖然 weak_ptr 可以避免循環引用,但使用時也要注意以下幾點:

  • 訪問前必須 lock():weak_ptr 不能直接使用,必須調用 lock() 方法獲取一個 shared_ptr 實例。

    if (auto a = b->a_ptr.lock()) {     // 安全使用 a } else {     // 對象已經被釋放 }
  • 可能為空:因為 weak_ptr 不控制生命周期,所以在使用時要判斷對象是否還存在。

  • 適合“觀察者”場景:比如緩存、回調注冊、事件監聽等不需要強引用的場合。


哪些場景容易出現循環引用?

除了上面的例子,還有一些常見情況容易出問題:

  • 樹形結構中父子節點互相引用(如父節點持有子節點的 shared_ptr,子節點反過來持有父節點的 shared_ptr)
  • 觀察者模式中,訂閱者和發布者之間互相持有指針
  • GUI 中控件之間的相互引用(比如按鈕和窗口)

這些場景都可以考慮將“被動方”的引用改為 weak_ptr 來打破循環。


基本上就這些。用 weak_ptr 替代不必要的 shared_ptr 是個好習慣,尤其是在設計類之間的關系時提前考慮引用方向,能有效避免很多潛在的內存泄漏問題。

? 版權聲明
THE END
喜歡就支持一下吧
點贊12 分享