std::shared_ptr循环引用会导致内存泄漏,因引用计数无法归零;应使用weak_ptr打破循环,仅一方用shared_ptr持有,另一方用weak_ptr观察,并通过lock()安全访问。

std::shared_ptr 循环引用会导致内存泄漏
当两个 std::shared_ptr 相互持有对方管理的对象时,引用计数永远无法归零,对象永远不会析构。这不是 bug,是设计使然——shared_ptr 只看“还有谁在用”,不关心“谁在用谁”。
典型场景:双向链表节点、父子对象(如树节点)、观察者与被观察者之间用 shared_ptr 互相保存。
现象:程序结束前内存占用持续增长,Valgrind 或 ASan 报告“still reachable”块,对象的析构函数根本不执行。
用 weak_ptr 打断循环中的一个引用链
weak_ptr 不增加引用计数,只“观察”对象是否还活着。它必须配合 lock() 升级为 shared_ptr 才能安全访问对象,且升级失败时返回空 shared_ptr。
立即学习“C++免费学习笔记(深入)”;
关键原则:循环中**只有一方用 shared_ptr 持有,另一方改用 weak_ptr**。比如父节点用 shared_ptr 持有子节点,子节点用 weak_ptr 回指父节点。
- 不要在构造函数里直接用
weak_ptr的lock()结果初始化成员shared_ptr,容易引发未定义行为 - 访问前必须调用
lock()并检查返回值,不能假设对象一定存在 -
weak_ptr本身不参与资源管理,析构开销极小,但频繁lock()有原子操作成本
示例:
struct Node {
std::shared_ptr<Node> next;
std::weak_ptr<Node> prev; // 不再用 shared_ptr
};
auto a = std::make_shared<Node>();
auto b = std::make_shared<Node>();
a->next = b;
b->prev = a; // 此时 b 不延长 a 的生命周期
weak_ptr::lock() 返回空 shared_ptr 的常见原因
不是所有 weak_ptr 失效都意味着你写错了,但得知道为什么失效:
- 被观察的
shared_ptr已全部析构(正常) - 对象在多线程中被另一个线程释放,而本线程的
weak_ptr还没来得及lock()(需同步) - 误把临时
shared_ptr赋给weak_ptr,比如wptr = std::make_shared<t>();</t>后立刻离开作用域,临时对象销毁
错误写法:
std::weak_ptr<int> wp;
{
auto sp = std::make_shared<int>(42);
wp = sp; // OK
} // sp 析构 → wp now expired
auto sp2 = wp.lock(); // sp2.get() == nullptr
std::enable_shared_from_this 是弱引用的辅助工具
当你需要从对象内部安全地生成指向自身的 shared_ptr(比如回调、异步任务),直接用 this 构造会破坏引用计数——此时必须继承 std::enable_shared_from_this<t></t>。
它内部持有一个 weak_ptr,由首次创建的 shared_ptr 初始化。所以:
- 必须通过
shared_ptr创建对象(如make_shared),否则shared_from_this()抛std::bad_weak_ptr -
weak_from_this()是 C++17 新增,可直接拿到那个内部weak_ptr,适合做延迟访问或观察 - 不要在构造函数里调用
shared_from_this(),此时内部weak_ptr还没被初始化
典型误用:
struct Good : std::enable_shared_from_this<Good> {
void go() {
auto self = shared_from_this(); // OK,前提是 this 来自 shared_ptr
}
};
// 错误:
Good g;
g.shared_from_this(); // 抛异常
循环引用真正的复杂点不在语法,而在逻辑所有权的划分:哪个模块“真正拥有”这个对象?weak_ptr 不是补丁,是所有权模型的显式声明。一旦开始用,就得全程按这个契约来——比如子节点访问父节点前,必须接受“父节点可能已不存在”这个事实。










