什么是shared_from_this ?
从msvc上摘下来的memory中关于shared_from_this
类的实现。还是比较好理解这个类在做什么的,内部有一个智能指针私有成员,不过是weak_ptr
类型的,公有方法中提供了shared_from_this
函数,如果调用这个函数,就会将weak_ptr
指针返回出去。
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 37 38 39 40 _EXPORT_STD template <class _Ty > class enable_shared_from_this { public : using _Esft_type = enable_shared_from_this; _NODISCARD shared_ptr<_Ty> shared_from_this () { return shared_ptr <_Ty>(_Wptr); } _NODISCARD shared_ptr<const _Ty> shared_from_this () const { return shared_ptr <const _Ty>(_Wptr); } _NODISCARD weak_ptr<_Ty> weak_from_this () noexcept { return _Wptr; } _NODISCARD 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 _Yty > friend class shared_ptr ; mutable weak_ptr<_Ty> _Wptr; };
应用场景
正如注释中说明的那样,会把当前对象用智能指针封装后抛出去。如果你的类中成员函数需要将自身所在的类对象作为参数传递出去的话,就能够用上这一个类了。因为成员函数并不知道传递出去后的类对象会被执行什么操作,如果直接将裸指针this传递出去的话,很有可能会被别的调用函数直接释放掉,那么就会造成二次析构的错误,本质上还是为了安全。
那么,将当前对象使用智能指针封装下再抛出去,就可以通过智能指针的引用计数来规避二次析构的错误。使用weak_ptr
类型而不是shared_ptr
类型的原因是为了防止经典的循环引用析构问题。
如何使用
使用起来相当简单,如果自定义的类预期自身将会被成员函数传递出去,那么就继承std::enable_shared_from_this
这个类,然后在需要传递的位置,使用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 class A ;void useA (shared_ptr<A>& a) { std::cout << "inside useA: use_count: " << a.use_count () << std::endl; } class A : public std::enable_shared_from_this<A> {public : void deliver () { auto self = shared_from_this (); cout << "before useA: use_count: " << self.use_count () << std::endl; useA (self); cout << "after useA: use_count: " << self.use_count () << std::endl; } }; int main () { std::shared_ptr<A> a (new A) ; a->deliver (); cout << "end of main: use_count: " << a.use_count () << std::endl; return 0 ; }
类A的成员函数需要调用一个名为useA
的外部函数,并将this对象作为参数传递过去,在每个步骤我们都打印一下引用计数,结果如下:
1 2 3 4 before useA: use_count: 2 inside useA: use_count: 2 after useA: use_count: 2 end of main: use_count: 1
这是我们想要的结果,在当前类对象作为参数传出去时,引用计数增加了,然后在成员函数执行结束后,引用数量重新回到了1。
写在最后(一些坑和注意事项)
如果传递this指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class A ;void useA (const shared_ptr<A>& a) { std::cout << "inside useA: use_count: " << a.use_count () << std::endl; } class A {public : void error_deliver () { useA (std::shared_ptr <A>(this )); } }; int main () { std::shared_ptr<A> a (new A) ; a->error_deliver (); cout << "end of main: use_count: " << a.use_count () << std::endl; return 0 ; }
终端中会打印如下:
1 2 inside useA: use_count: 1 end of main: use_count: 1
然后程序崩溃。
分析代码逻辑可以发现,我们将裸指针封装成了智能指针传递出去了,这导致了引用计数错误。
具体问题出现在传参的这一步std::shared_ptr<A>(this)
,我们为了将this转换成智能指针传递出去,又构造了一个智能指针临时对象,那么这个时候就出现了两个独立的智能指针对象,但内部都是指向了同一个内存地址,所以在useA
中打印了一个智能指针对象的引用计数是1,而在main函数中打印的是另一个智能指针对象的引用计数也是1,问题在于error_deliver
中生成的临时智能指针对象,最多也就活到useA
执行完,而这个智能指针在析构时,发现自己当前的引用计数是1,这意味着要同时释放掉对应的内存,在这个时候对象a就已经被释放掉了。后面在main函数中虽然打印了引用计数还是1,但是指向的那块内存地址已经是非法的了,所以在main函数退出再次析构对象a时,发生了崩溃。
既然智能指针不行,直接传裸指针吧 ,不是不可以,但只能抱希望于调用的函数不会将裸指针析构掉,或者你不会在调用函数执行前析构掉this。比如这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class A ;void useA (A* a) ;class A {public : void error_deliver () { useA (this ); } }; void useA (A* a) { delete a; } int main () { std::shared_ptr<A> a (new A) ; a->error_deliver (); cout << "end of main: use_count: " << a.use_count () << std::endl; return 0 ; }
如出一辙,还是会在程序退出时崩溃,还是因为在useA
中delete了指针。悲催的是useA
这个函数往往并不受我们的控制,比如是三方库中的接口函数,我们只知道如何调用,而不知道内部具体做了什么。这就埋下了很大的风险。
使用shared_from_this不能是栈上的对象
简单来讲,这个类的设计目的就是给智能指针用的,而不是给直接构造出来的对象用的。
1 2 3 4 5 int main () { A a; a.deliver (); return 0 ; }
这样写的话在调用shared_from_this
时会报错,不使用智能指针,直接构造出来的对象不能使用shared_from_this
获得指向自身的智能指针。
如果仔细地研究了enable_shared_from_this
类的话会发现,在这个类中并没有对_Wptr
这个成员进行初始化,事实上,对这个成员初始化的过程发生在智能指针构造的时候,所以shared_from_this
只能是智能指针构造出来的对象才能使用。