C++智能指针std::shared_ptr的循环引用解决方案
C++标准库中的std::shared_ptr通过引用计数自动管理动态内存,但当两个或多个对象互相持有对方的shared_ptr时,会形成循环引用,导致引用计数永远不会归零,从而引发内存泄漏。解决这一问题的核心方法是使用std::weak_ptr打破循环。
识别循环引用场景
观察以下典型结构:两个类(如Parent和Child)互相包含指向对方的shared_ptr成员变量。例如:
#include <memory>
#include <iostream>
class Child;
class Parent {
public:
std::shared_ptr<Child> child_ptr;
~Parent() { std::cout << "Parent destroyed\n"; }
};
class Child {
public:
std::shared_ptr<Parent> parent_ptr;
~Child() { std::cout << "Child destroyed\n"; }
};
创建相互引用的对象后,即使离开作用域,析构函数也不会被调用:
void create_cycle() {
auto parent = std::make_shared<Parent>();
auto child = std::make_shared<Child>();
parent->child_ptr = child;
child->parent_ptr = parent;
} // 函数结束,但对象未销毁
运行此代码会发现控制台无任何析构输出,证明内存未被释放。
使用std::weak_ptr打破循环
std::weak_ptr是一种不增加引用计数的观察型智能指针。它必须由shared_ptr构造,并通过lock()方法临时转换为shared_ptr以访问对象。
修改其中一个方向的指针类型为weak_ptr。通常将“从属”关系的一方设为弱引用(如Child持有Parent的弱引用):
#include <memory>
#include <iostream>
class Child;
class Parent {
public:
std::shared_ptr<Child> child_ptr;
~Parent() { std::cout << "Parent destroyed\n"; }
};
class Child {
public:
std::weak_ptr<Parent> parent_ptr; // 改为 weak_ptr
~Child() { std::cout << "Child destroyed\n"; }
};
重新测试对象生命周期:
void create_fixed_cycle() {
auto parent = std::make_shared<Parent>();
auto child = std::make_shared<Child>();
parent->child_ptr = child;
child->parent_ptr = parent; // weak_ptr 赋值
} // 函数结束,对象正常销毁
此时控制台会输出:
Child destroyed
Parent destroyed
证明循环已被打破。
安全访问weak_ptr指向的对象
由于weak_ptr不保证对象存活,必须通过lock()获取临时shared_ptr再使用:
- 调用
weak_ptr::lock()尝试获取有效的shared_ptr。 - 检查返回的
shared_ptr是否为空(即原对象是否已被销毁)。 - 仅当非空时才访问对象成员。
void access_parent_safely(const std::weak_ptr<Parent>& wp) {
if (auto sp = wp.lock()) {
// sp 是有效的 shared_ptr,可安全使用
std::cout << "Parent is alive\n";
} else {
std::cout << "Parent already destroyed\n";
}
}
切勿直接通过weak_ptr解引用(如*wp),这会导致编译错误。
常见应用场景与最佳实践
| 场景 | 强引用方向 | 弱引用方向 |
|---|---|---|
| 父子对象(如树节点) | 父 → 子 | 子 → 父 |
| 观察者模式 | 被观察者 → 观察者列表 | 观察者 → 被观察者 |
| 缓存系统 | 缓存容器 → 对象 | 对象 → 缓存容器(若需要反向查找) |
遵循以下原则选择弱引用方向:
- 保持“拥有权”关系的指针为
shared_ptr(如容器拥有元素)。 - 将“反向导航”或“观察”关系的指针设为
weak_ptr。 - 避免在性能敏感路径频繁调用
lock();若需多次访问,应缓存lock()结果。
注意:weak_ptr不能直接解引用,也不能用于数组或自定义删除器场景(除非配合shared_ptr)。
验证修复效果
编写完整测试程序确认内存正确释放:
#include <memory>
#include <iostream>
class Child;
class Parent {
public:
std::shared_ptr<Child> child_ptr;
~Parent() { std::cout << "Parent destroyed\n"; }
};
class Child {
public:
std::weak_ptr<Parent> parent_ptr;
~Child() { std::cout << "Child destroyed\n"; }
void print_parent_status() {
if (parent_ptr.lock()) {
std::cout << "Parent is alive during Child's lifetime\n";
}
}
};
int main() {
{
auto parent = std::make_shared<Parent>();
auto child = std::make_shared<Child>();
parent->child_ptr = child;
child->parent_ptr = parent;
child->print_parent_status();
} // 作用域结束
std::cout << "Exited scope\n";
return 0;
}
预期输出:
Parent is alive during Child's lifetime
Child destroyed
Parent destroyed
Exited scope
析构顺序表明:child先销毁(因其被parent强引用),随后parent因无外部引用而销毁。循环引用问题已解决。

暂无评论,快来抢沙发吧!