C++智能指针enable_shared_from_this的循环引用解决
在C++开发中,使用 std::shared_ptr 管理对象生命周期时,常遇到两个核心痛点:如何在类成员函数中安全地获取指向当前对象的 shared_ptr,以及如何由此引发的循环引用导致内存泄漏问题。直接传递 this 指针会导致独立的引用计数块,造成对象被多次销毁;而对象间相互持有 shared_ptr 则会导致引用计数归零失败。
本指南将详细介绍如何结合使用 std::enable_shared_from_this 和 std::weak_ptr 来彻底解决这一技术难题。
1. 识别问题的根源
在编写涉及回调或异步操作的类时,经常需要将当前对象的指针传递给另一个对象。如果直接使用 this,并在接收方将其封装为 std::shared_ptr,将引发严重的内存错误。
错误操作示例:
class Node {
public:
void setCallback() {
// 错误:直接使用 this 构造新的 shared_ptr
// 这会创建一个新的、独立的引用计数块,与现有的 shared_ptr 管理断开
std::shared_ptr<Node> self(this);
}
};
后果逻辑分析:
当上述代码执行时,内存中会出现两个互不相干的引用计数控制系统。
2. 启用安全自引用机制
为了在类成员函数内部获取一个与当前对象引用计数关联的 shared_ptr,C++ 提供了 std::enable_shared_from_this 模板基类。
执行步骤:
- 继承 模板类
std::enable_shared_from_this,并将当前类名作为模板参数传递。 - 确保对象本身是通过
std::make_shared或std::shared_ptr构造的,不能是栈对象或裸指针new的对象。
代码实现:
#include <memory>
#include <iostream>
class Controller : public std::enable_shared_from_this<Controller> {
public:
// 一定要通过 shared_ptr 构造,否则 shared_from_this 会抛出异常
static std::shared_ptr<Controller> create() {
return std::make_shared<Controller>();
}
void registerSelf() {
// 正确:调用 shared_from_this() 获取关联的 shared_ptr
std::shared_ptr<Controller> self = shared_from_this();
std::cout << "Registering self, use_count: " << self.use_count() << std::endl;
}
};
3. 解决循环引用
循环引用通常发生在两个对象相互持有对方的 shared_ptr。例如,Controller 持有 Worker 的强引用,Worker 回调时也持有 Controller 的强引用。此时引用计数永远不会降为 0。
解决方案策略:
- 确定持有关系:决定谁是“所有者”,谁是“观察者”。
- 打破环:在“观察者”一方,将持有的
shared_ptr替换为std::weak_ptr。 - 传递指针:在建立联系时,使用
shared_from_this()传递Controller的指针。
操作步骤:
- 在
Worker类中,定义成员变量为std::weak_ptr<Controller>。 - 在
Worker的方法中,调用lock()方法将weak_ptr提升为shared_ptr以便临时使用(使用前必须检查是否有效)。 - 在
Controller类中,调用shared_from_this()将自身传递给Worker。
完整代码示例:
#include <iostream>
#include <memory>
#include <string>
// 前置声明
class Worker;
class Controller : public std::enable_shared_from_this<Controller> {
public:
std::string name;
std::shared_ptr<Worker> worker;
Controller(std::string n) : name(n) {
std::cout << "Controller " << name << " Created.\n";
}
~Controller() {
std::cout << "Controller " << name << " Destroyed.\n";
}
void setWorker(std::shared_ptr<Worker> w) {
worker = w;
// 关键步骤:将当前对象的 shared_ptr 传递给 Worker
// 使用 shared_from_this 而不是 this
worker->setController(shared_from_this());
}
};
class Worker {
public:
// 使用 weak_ptr 打破循环引用
std::weak_ptr<Controller> controller;
void setController(std::shared_ptr<Controller> c) {
controller = c;
}
void doWork() {
// 必须先 lock() 提升为 shared_ptr
if (auto ptr = controller.lock()) {
std::cout << "Worker is working for " << ptr->name << "\n";
} else {
std::cout << "Controller is gone.\n";
}
}
};
int main() {
// 1. 创建 Controller
auto ctrl = std::make_shared<Controller>("MainServer");
// 2. 创建 Worker
auto wk = std::make_shared<Worker>();
// 3. 建立关系
// ctrl 持有 wk (shared_ptr)
// wk 持有 ctrl (weak_ptr)
ctrl->setWorker(wk);
// 4. 模拟工作
wk->doWork();
// 5. 结束作用域
// ctrl 引用计数归零,析构
// wk 中的 weak_ptr 不阻止 ctrl 销毁
// wk 随后也因 ctrl 析构而释放(这里 ctrl 也不持有 wk 的引用了?
// 注意:本例中 ctrl 持有 wk 的 shared_ptr,所以 wk 在 ctrl 析构后才会析构,
// 但因为没有外部强引用指向 wk,ctrl 销毁后 wk 的强引用计数归零,随之销毁。
return 0;
}
执行逻辑解析:
下表展示了引用计数的变化情况及对象生命周期。
| 阶段 | ctrl (强引用计数) |
wk (强引用计数) |
wk.controller (弱引用) |
状态说明 |
|---|---|---|---|---|
| 创建对象 | 1 | 1 | 0 | 对象独立存在 |
| 建立关系 | 1 | 2 | 1 | ctrl 引用 wk,wk 弱引用 ctrl |
| 作用域结束 | 0 (析构) | 1 (随后变为0) | - | ctrl 销毁,wk 失去持有者,随之销毁 |
| 最终结果 | 0 | 0 | - | 内存正常释放,无泄漏 |
4. 关键注意事项总结
在使用此模式时,请务必遵守以下规则,否则可能导致程序崩溃:
-
禁止在构造函数中调用
shared_from_this()。- 原因:在构造函数执行期间,
std::shared_ptr对象尚未完全构建完成,内部的weak_ptr初始化机制尚未生效。此时调用会抛出std::bad_weak_ptr异常。
- 原因:在构造函数执行期间,
-
检查
lock()返回值。weak_ptr::lock()可能返回空的shared_ptr(如果对象已被销毁)。直接使用返回的指针而不判空是极其危险的。
-
不要混合使用裸指针和智能指针。
- 如果一个对象被
shared_ptr管理,就不要在其他地方保存它的裸this指针并试图将其转回shared_ptr,除非你使用shared_from_this。
- 如果一个对象被
通过上述步骤,你可以构建出既安全又无内存泄漏的 C++ 对象网络。

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