22. enable_shared_from_this:原理与使用场景

说实话,我刚接触C++11那会儿,看到enable_shared_from_this这个类模板,第一反应是:这名字也太长了吧?后来在项目中踩了坑,才明白这东西有多重要。

今天我们就来聊聊它。我会从原理讲到实战,顺便分享一些我踩过的坑。

一个让人头疼的场景

先看个例子。假设你有一个Task类,它需要把自己传给某个回调函数:

class Task {
public:
    void start() {
        // 启动一个异步操作
        // 操作完成后,回调这个对象的 onComplete
        async_operation([this]() {
            this->onComplete();
        });
    }

    void onComplete() {
        std::cout << "Task completed\n";
    }
};

这段代码有什么问题?

嗯,问题大了。如果Task对象在异步操作完成前就被销毁了,this指针就成了悬空指针。回调执行时,程序直接崩溃。

你可能会说:那用shared_ptr不就行了?

class Task {
public:
    void start() {
        auto self = shared_from_this();  // 编译错误!
        async_operation([self]() {
            self->onComplete();
        });
    }
};

等等,shared_from_this()不是随便就能用的。它要求你的类必须继承自enable_shared_from_this

enable_shared_from_this 是什么

说白了,enable_shared_from_this是一个辅助类模板。它让你的类能够安全地生成指向自己的shared_ptr

用法很简单:

#include <memory>

class Task : public std::enable_shared_from_this<Task> {
public:
    void start() {
        auto self = shared_from_this();
        async_operation([self]() {
            self->onComplete();
        });
    }

    void onComplete() {
        std::cout << "Task completed\n";
    }
};

现在,shared_from_this()会返回一个shared_ptr<Task>,它和外部管理这个对象的shared_ptr共享所有权。只要这个shared_ptr还在,对象就不会被销毁。

原理:它是怎么做到的?

我当年第一次看到这个,心里就犯嘀咕:this指针本身不包含引用计数信息,shared_from_this()怎么知道引用计数是多少?

答案其实不复杂。enable_shared_from_this内部维护了一个弱指针weak_ptr),指向对象自身。当你第一次用shared_ptr管理这个对象时,这个弱指针会被初始化。

来看个简化版的实现:

template<typename T>
class enable_shared_from_this {
private:
    mutable std::weak_ptr<T> weak_this;

protected:
    enable_shared_from_this() = default;

public:
    shared_ptr<T> shared_from_this() {
        return weak_this.lock();  // 从弱指针提升为共享指针
    }
};

关键点在于:weak_this是在shared_ptr的构造函数中被赋值的。shared_ptr的构造函数会检测到T继承自enable_shared_from_this,然后自动设置这个弱指针。

核心流程:

  1. 外部创建shared_ptr<Task>时,shared_ptr构造函数检测到Task继承自enable_shared_from_this
  2. shared_ptr内部设置enable_shared_from_this::weak_this指向自己
  3. 调用shared_from_this()时,从weak_this提升出一个新的shared_ptr
  4. 这个新shared_ptr和原来的shared_ptr共享同一个控制块

SVG:enable_shared_from_this 工作原理图

enable_shared_from_this 工作原理 外部 shared_ptr<Task> 控制块(引用计数) Task 对象 指向 管理 enable_shared_from_this 内部 weak_ptr<Task> weak_this shared_ptr构造时设置 shared_from_this() 调用 返回 shared_ptr<Task> weak_this.lock() 从弱指针提升为共享指针,共享同一个控制块

使用场景:什么时候该用它?

我个人经验里,以下三种场景最常用到enable_shared_from_this

1. 异步回调中需要延长对象生命周期

这是最常见的场景。就像我们前面那个Task的例子。异步操作需要确保对象在回调执行时还活着。

class Downloader : public std::enable_shared_from_this<Downloader> {
public:
    void download(const std::string& url) {
        auto self = shared_from_this();
        http_client->get(url, [self](const Response& resp) {
            self->onDownloadComplete(resp);
        });
    }

private:
    void onDownloadComplete(const Response& resp) {
        // 处理下载结果
    }
};

2. 对象需要注册到多个观察者中

我记得有个项目里,一个Sensor对象需要同时注册到多个Monitor中。如果直接用this指针,一旦Sensor被销毁,Monitor里的指针就悬空了。

class Sensor : public std::enable_shared_from_this<Sensor> {
public:
    void registerTo(Monitor* monitor) {
        monitor->addSensor(shared_from_this());
    }
};

3. 实现链式调用或工厂方法

有时候你需要从成员函数中返回shared_ptr给调用者:

class Node : public std::enable_shared_from_this<Node> {
public:
    std::shared_ptr<Node> getShared() {
        return shared_from_this();
    }
};

避坑指南

我曾经踩过的坑,你千万别再踩了:

  • 不要在构造函数中调用 shared_from_this()——对象还没被shared_ptr管理,弱指针还没初始化,调用会抛出std::bad_weak_ptr异常
  • 不要在栈上创建对象——enable_shared_from_this要求对象必须由shared_ptr管理,否则shared_from_this()会崩溃
  • 小心循环引用——如果回调中捕获的shared_ptr形成了循环,对象就永远无法释放了

小技巧:如果你不确定对象是否被shared_ptr管理,可以用weak_from_this()(C++17引入)先获取一个weak_ptr,再通过lock()安全提升。这样即使对象已经被销毁,也不会崩溃。

一个完整的例子

最后,给你看一个我实际项目中用过的模式。这个Timer类可以安全地在回调中引用自己:

#include <memory>
#include <functional>
#include <iostream>

class Timer : public std::enable_shared_from_this<Timer> {
public:
    static std::shared_ptr<Timer> create() {
        return std::shared_ptr<Timer>(new Timer());
    }

    void start(int interval_ms) {
        auto self = shared_from_this();
        timer_impl_ = std::thread([self, interval_ms]() {
            while (self->running_) {
                std::this_thread::sleep_for(
                    std::chrono::milliseconds(interval_ms));
                self->onTick();
            }
        });
    }

    void stop() {
        running_ = false;
        if (timer_impl_.joinable()) {
            timer_impl_.join();
        }
    }

private:
    Timer() : running_(true) {}

    void onTick() {
        std::cout << "Tick...\n";
    }

    std::thread timer_impl_;
    std::atomic<bool> running_;
};

注意这里用了create()工厂方法,确保对象只能通过shared_ptr创建。构造函数是私有的,外部没法在栈上创建Timer对象。

嗯,这就是enable_shared_from_this的核心内容。说白了,它就是帮你安全地在对象内部生成shared_ptr,避免悬空指针的问题。记住那三个避坑点,基本就不会出大问题。


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