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 指向同一个对象,那就会有两个独立的控制块,各自认为自己是唯一的管理者。结果就是——双重释放,程序崩溃。

⚠️ 我曾经在线上环境踩过这个坑。 一个网络连接对象被两个不同的 shared_ptr 管理,析构时触发了两次 delete,直接 core dump。排查了整整一个下午才定位到问题。

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 管理着。

核心原则: 调用 shared_from_this() 之前,对象必须已经存在于一个 shared_ptr 中。否则会抛出 std::bad_weak_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 共享同一个控制块,引用计数自然就正确了。

我画了一张图帮你理解这个流程:

enable_shared_from_this 内部原理 MyClass 对象 成员数据 weak_ptr<MyClass> (继承自 enable_shared_from_this) 外部 shared_ptr 控制块指针 控制块 (Control Block) 引用计数: 2 弱引用计数: 1 (shared_from_this 提升时) 指向 管理 weak_ptr 观察 shared_from_this() 从 weak_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();  // 正常工作
💡 我个人习惯: 在类的构造函数里绝对不要调用 shared_from_this()。因为此时对象还没被 shared_ptr 完全接管,weak_ptr 还是空的。我一般会在一个专门的初始化函数里做这件事。

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 的经典用法。

关键点: 异步回调中捕获 shared_from_this() 而不是裸 this,可以避免悬空指针问题。这是 C++ 网络编程中的标准做法。

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 管理
  • 不要在构造函数和析构函数中调用

嗯,掌握了这些,你就能在异步编程、回调注册等场景中游刃有余了。


公众号:蓝海资料掘金营,微信deep3321