C++中的std::shared_ptr循环引用怎么解决?(使用std::weak_ptr打破环)

9次阅读

std::shared_ptr循环引用会导致内存泄漏,因引用计数无法归零;应使用std::weak_ptr在非拥有权方向打破循环,访问前必须调用lock()检查有效性。

C++中的std::shared_ptr循环引用怎么解决?(使用std::weak_ptr打破环)

std::shared_ptr 循环引用会导致内存泄漏

当两个或多个 std::shared_ptr 相互持有对方管理的对象时,引用计数永远无法归零,对象不会被析构——这不是悬空指针问题,而是资源永久驻留。典型场景是父子类双向关联、观察者与被观察者、图结构节点互相引用等。

用 std::weak_ptr 替换循环中的一方持有关系

std::weak_ptr 不增加引用计数,只“观察”所指向对象是否还存活。它不能直接解引用,必须先调用 lock() 转为 std::shared_ptr,失败则说明对象已被释放。

  • 只在“非拥有权”方向使用 std::weak_ptr:比如子节点持父节点的 std::weak_ptr,而非 std::shared_ptr
  • 访问前必须检查:调用 lock() 后判断返回的 std::shared_ptr 是否为空
  • 不能用 weak_ptr.get() 或直接解引用 —— 编译不通过,强制你处理生命周期不确定性

一个典型的父子类循环引用修复示例

下面代码中,Parent 持有 Childstd::shared_ptr,而 Child 改用 std::weak_ptr 持有 Parent,从而打破循环:

#include  #include   struct Parent; struct Child;  struct Parent {     std::shared_ptr child;     ~Parent() { std::cout << "Parent destroyedn"; } };  struct Child {     std::weak_ptr parent; // ← 关键:不增加引用计数     ~Child() { std::cout << "Child destroyedn"; } };  int main() {     auto p = std::make_shared();     auto c = std::make_shared();     p->child = c;     c->parent = p; // weak_ptr assignment,无引用计数变化      // 安全访问 parent(需检查 lock() 结果)     if (auto parent_ptr = c->parent.lock()) {         std::cout << "Parent still aliven";     } else {         std::cout << "Parent already gonen";     } } // ← 此处 p 和 c 都会正确析构

容易忽略的细节和陷阱

很多人以为只要用了 std::weak_ptr 就万事大吉,但实际还有几个关键点:

立即学习C++免费学习笔记(深入)”;

  • std::weak_ptr 本身不保证线程安全:多个线程同时调用 lock() 是安全的,但后续对所获 shared_ptr 的使用仍需同步
  • 不能用 std::weak_ptr 初始化另一个 std::shared_ptr(如 std::shared_ptr(weak_ptr) 会抛 std::bad_weak_ptr 异常),必须用 lock()
  • 析构函数中调用 lock() 可能返回空 —— 因为此时对象可能正处在销毁链中,其他 shared_ptr 已释放
  • 若存在多重间接引用(A→B→C→A),仅改一处 weak_ptr 不够,得识别整个环并至少打断一处

循环引用不是编译期错误,也不会报 warning,只有在程序长期运行后发现内存持续上涨,或者用 ASan/Valgrind 检测到未释放块时才暴露。一旦怀疑,优先检查所有双向 shared_ptr 成员,把“被依赖方”换成 weak_ptr

text=ZqhQzanResources