一. 引入
简单地说:enable_shared_from_this
是为了解决 在类的内部获取自己的 shared_ptr 这件事情而存在的。
众所周知,每一个对象都能通过 this
指针来访问自己的地址。this
指针也是所有成员函数的隐含参数。然而有些时候,我们需要的不仅是 this,而是一个 “this 的智能指针”。
这里有一个常见的场景:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class A { public: A() : did_it_(false) {} ~A() { std::cout << "destoried" << std::endl; }
void OnDo(bool did) { did_it_ = did; std::cout << "somthing did" << std::endl; }
void DoSth_Async() { std::thread t([this]() { std::this_thread::sleep_for(std::chrono::seconds(5)); OnDo(true); }); t.detach(); }
private: bool did_it_; };
|
代码如上:在异步方法 DoSth_Async()
中调用了成员方法 OnDo(bool)
。这里存在一个问题:当 OnDo()
被调用的时候,该类的对象是否还在存活?
1 2 3 4 5 6 7 8
| int main(){ { std::shared_ptr<A> ptr(new A()); ptr->DoSth_Async(); } std::this_thread::sleep_for(std::chrono::seconds(5)); return 0; }
|
智能指针 ptr
在出作用域后立即被释放。所以当 OnDo()
被调用的时候,其所在的对象实际已经被释放了。如果要确保在 OnDo()
被调用的时候,该对象仍然在生命周期内呢?
一个方便的方法就是在构建线程的时候,将该对象的 shared_ptr 传入到线程中。在该线程的生命周期内,该对象就会一直存在。这是一种利用 shared_ptr 的 保活机制。
此时,enable_shared_from_this
就有存在的必要了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| class A : public std::enable_shared_from_this<A> { public: A() : did_it_(false) {} ~A() { std::cout << "destoried" << std::endl; }
void OnDo(bool did) { did_it_ = did; std::cout << "somthing did" << std::endl; }
void DoSth_Async() { auto self = shared_from_this(); std::thread t([this, self]() { std::this_thread::sleep_for(std::chrono::seconds(3)); OnDo(true); }); t.detach(); }
private: bool did_it_; };
|
enable_shared_from_this
是一个模板类。它一般用作基类,它的成员 shared_from_this()
、weak_from_this()
可以使继承此类的类从当前对象获取其本身的 shared_ptr
或者 weak_ptr
并且增加引用计数。
我们直接使用 this
指针来构建自身的 shared_ptr
不可以吗?就像下面代码所表现的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class C { public: std::shared_ptr<C> GetSelf() { return std::shared_ptr<C>(this); }
void DoSomthing() { auto ptr = GetSelf(); std::cout << ptr.use_count() << std::endl; } };
int main() { std::shared_ptr<C> ptr_c(new C()); ptr_c->DoSomthing(); std::cout << ptr_c.use_count() << std::endl; return 0; }
|
这种方法在使用的时候可能看不出问题,但是在对象析构的时候将会出现问题:一个对象将被释放两次。在 DoSomthing()
方法结束后它将释放一次,在 main()
函数完成后又将释放一次。
究其原因,GetSelf()
构造智能指针时,其引用计数并没有自增。
二. 原理
enable_shared_from_this
位于 <memory>
头文件中,其实现非常简单:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| template<class _Ty> class enable_shared_from_this { public: using _Esft_type = enable_shared_from_this;
shared_ptr<_Ty> shared_from_this() { return shared_ptr<_Ty>(_Wptr); }
shared_ptr<const _Ty> shared_from_this() const { return shared_ptr<const _Ty>(_Wptr); }
weak_ptr<_Ty> weak_from_this() noexcept { return _Wptr; }
weak_ptr<const _Ty> weak_from_this() const noexcept { return _Wptr; }
protected: constexpr enable_shared_from_this() noexcept : _Wptr() {} enable_shared_from_this(const enable_shared_from_this&) noexcept : _Wptr() {} enable_shared_from_this& operator=(const enable_shared_from_this&) noexcept { return *this; } ~enable_shared_from_this() = default;
private: template<class _Other, class _Yty> friend void _Enable_shared_from_this1(const shared_ptr<_Other>& _This, _Yty* _Ptr, true_type);
mutable weak_ptr<_Ty> _Wptr; };
|
其中友元 _Enable_shared_from_this1()
会被 shared_ptr
调用。在这个友元函数中,会尝试着给私有成员 _Wptr
赋值。
当 shared_from_this()
被调用时,使用 _Wptr
构造一个 shared_ptr
,此时引用计数加 1。
三. 陷阱
1. 在原生指针的对象里调用了 shared_from_this
如下代码:
1 2
| A* a = new A(); a->DoSth_Async();
|
由于没有使用 shared_ptr
,友元 _Enable_shared_from_this1()
不会被调用,此时 _Wptr
是 empty 的。如果强行调用 shared_from_this()
将会引发异常:
1
| exception : std::bad_weak_ptr
|
2. 过早调用 shared_from_this()
代码如下:在构造函数中调用了 shared_from_this()
,此时还没有给 _Wptr
赋值,会引发同样的异常。
1 2 3 4 5 6
| class Sample : public std::enable_shared_from_this<Sample> { public: Sample() { auto ptr = shared_from_this(); } };
|
3. 关于继承
在一棵继承树里重复继承 std::enable_shared_from_this
会引发 编译错误。
如果需要在子类中使用 shared_from_this()
,可以这样写:
1 2 3 4 5 6 7 8 9 10 11
| class Super : public std::enable_shared_from_this<Super> { public: virtual ~Super() {} };
class Sub : public Super { public: std::shared_ptr<Sub> shared_from_this() { return std::dynamic_pointer_cast<Sub>(Super::shared_from_this()); } };
|