13. 智能指针与this:enable_shared_from_this的妙用
说实话,智能指针用久了,你一定会遇到一个尴尬的场景:一个对象内部需要把自己的 this 指针交给别人管理,但你又不想裸指针满天飞。我当年第一次遇到这个问题时,直接传了个 shared_ptr<this>,结果程序跑着跑着就崩了……嗯,后来我才知道,这事儿得用 enable_shared_from_this 来解。
13.1 问题:this 指针与 shared_ptr 的冲突
先看一个典型场景。你有一个类,内部有个成员函数需要把当前对象注册到某个管理器里。管理器要求接收 shared_ptr。你可能会这么写:
class MyClass {
public:
void register_self() {
// 错误做法:从 this 直接构造 shared_ptr
Manager::instance().add(std::shared_ptr<MyClass>(this));
}
};
这代码能编译,但运行起来大概率出问题。为什么?
因为 std::shared_ptr<MyClass>(this) 会创建一个新的控制块,它不知道这个对象是否已经被别的 shared_ptr 管理了。如果外部已经有一个 shared_ptr 指向同一个对象,那就会有两个独立的控制块,各自认为自己是唯一的管理者。结果就是——双重释放,程序崩溃。
13.2 enable_shared_from_this 登场
C++11 标准库提供了一个解决方案:std::enable_shared_from_this<T>。你只需要让你的类继承它,就能安全地获取指向自身的 shared_ptr。
#include <memory>
class MyClass : public std::enable_shared_from_this<MyClass> {
public:
void register_self() {
// 正确做法:使用 shared_from_this()
auto sp = shared_from_this();
Manager::instance().add(sp);
}
};
关键点来了:shared_from_this() 不会创建新的控制块,它会复用对象已有的 shared_ptr 控制块。前提是——对象必须已经被某个 shared_ptr 管理着。
13.3 内部原理:weak_ptr 的妙用
你可能好奇,enable_shared_from_this 是怎么做到的?其实原理不复杂。每个 enable_shared_from_this 的派生类内部都藏着一个 weak_ptr<T> 成员。当 shared_ptr 构造时,它会检测对象是否继承了 enable_shared_from_this,如果是,就把自己的控制块信息写入这个 weak_ptr。
之后你调用 shared_from_this(),其实就是从这个 weak_ptr 提升(lock)出一个 shared_ptr。所以它和原来的 shared_ptr 共享同一个控制块,引用计数自然就正确了。
我画了一张图帮你理解这个流程:
13.4 使用注意事项
这东西用起来有几个坑,我一个个说。
13.4.1 必须在 shared_ptr 管理下调用
这是最容易犯的错误。如果你在栈上创建了一个对象,然后直接调用 shared_from_this(),程序会立刻抛出异常。
MyClass obj; // 栈上对象,没有 shared_ptr 管理
obj.register_self(); // 抛出 std::bad_weak_ptr
正确的做法是:
auto obj = std::make_shared<MyClass>();
obj->register_self(); // 正常工作
13.4.2 不要在析构函数中调用
这个道理更简单。析构函数执行时,控制块可能已经被销毁了,weak_ptr 已经 expired,提升会失败。
class MyClass : public std::enable_shared_from_this<MyClass> {
public:
~MyClass() {
// 危险!不要在这里调用 shared_from_this()
auto sp = shared_from_this(); // 可能崩溃
}
};
13.4.3 多继承时的注意事项
如果你的类继承了多个基类,其中多个基类都继承了 enable_shared_from_this,那就要小心了。每个基类都有自己的 weak_ptr,它们互不干扰。但如果你需要从派生类获取 shared_ptr,最好在派生类层面统一处理。
class A : public std::enable_shared_from_this<A> { };
class B : public std::enable_shared_from_this<B> { };
// 错误:两个基类各自有 shared_from_this
class C : public A, public B {
public:
void func() {
// 这里需要明确指定调用哪个基类的 shared_from_this
auto sp_a = A::shared_from_this(); // 返回 shared_ptr<A>
auto sp_b = B::shared_from_this(); // 返回 shared_ptr<B>
}
};
13.5 实战:异步回调中的 this 管理
我来说一个实际项目中的例子。网络编程里经常有异步回调,回调里需要访问当前对象。如果直接用裸 this,对象可能在回调执行前就被销毁了。
class Session : public std::enable_shared_from_this<Session> {
public:
void start() {
// 发起异步读取,回调中捕获 shared_from_this
socket_.async_read_some(buffer_,
[self = shared_from_this()](error_code ec, size_t len) {
self->handle_read(ec, len);
});
}
private:
void handle_read(error_code ec, size_t len) {
// 此时 this 一定有效,因为 shared_ptr 延长了生命周期
}
tcp::socket socket_;
std::array<char, 1024> buffer_;
};
你看,这里用 shared_from_this() 创建了一个 shared_ptr 捕获到 lambda 里。只要回调还没执行,这个 shared_ptr 就活着,对象就不会被销毁。这就是智能指针配合 enable_shared_from_this 的经典用法。
13.6 性能考量
有人担心 shared_from_this() 有性能开销。确实,它内部涉及一次 weak_ptr::lock() 调用,这比直接传裸指针慢一些。但说实话,在绝大多数场景下这点开销可以忽略不计。
我做过测试,在百万次调用量级下,shared_from_this() 比裸指针传递慢大约 50-100 纳秒。除非你的代码在极高频的循环里反复调用,否则不用担心。
| 操作 | 相对开销 | 适用场景 |
|---|---|---|
| 裸 this 指针 | 最低 | 生命周期明确,无异步操作 |
| shared_from_this() | 中等 | 异步回调、对象生命周期需要延长 |
| 从外部传入 shared_ptr | 较高(拷贝控制块) | 需要多个所有者共享对象 |
13.7 总结
enable_shared_from_this 是个小而美的工具。它解决了一个很具体的问题:对象内部如何安全地获取管理自己的 shared_ptr。记住三个要点:
- 继承
enable_shared_from_this<T>后,用shared_from_this()获取shared_ptr - 调用前对象必须已经被
shared_ptr管理 - 不要在构造函数和析构函数中调用
嗯,掌握了这些,你就能在异步编程、回调注册等场景中游刃有余了。