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,然后自动设置这个弱指针。
核心流程:
- 外部创建
shared_ptr<Task>时,shared_ptr构造函数检测到Task继承自enable_shared_from_this shared_ptr内部设置enable_shared_from_this::weak_this指向自己- 调用
shared_from_this()时,从weak_this提升出一个新的shared_ptr - 这个新
shared_ptr和原来的shared_ptr共享同一个控制块
SVG:enable_shared_from_this 工作原理图
使用场景:什么时候该用它?
我个人经验里,以下三种场景最常用到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