如何用enable_shared_from_this 来得到指向自身的shared_ptr 及对enable_shared_from_this 的理解
在看《Linux多线程服务端编程:使用muduo C++网络库》 的时候,在说到如何防止在将对象的 this 指针作为返回值返回给了调用者时可能会造成的 core dump。需使用 enable_share_from_this。
首先要说明的一个问题是如何安全地将 this 指针返回给调用者。一般来说,我们不能直接将 this 指针返回。想象这样的情况,该函数将 this 指针返回到外部某个变量保存,然后这个对象自身已经析构了,但外部变量并不知道,那么此时如果外部变量使用这个指针,就会使得程序崩溃。
那么使用智能指针 shared_ptr 呢?当然,使得 c++ 指针安全目前来说最有效也使用最多的办法就是使用智能指针 shared_ptr。但问题是这里要怎么去使用它。首先假设你已经理解shared_ptr 的工作原理。然后我们来看下面一份代码:
#include <iostream> #include <memory> class Bad { public: Bad() { std::cout << "Bad()" << std::endl; } ~Bad() { std::cout << "~Bad()" << std::endl; } std::shared_ptr<Bad> getPtr() { return std::shared_ptr<Bad>(this); } }; int main(int argc, char const *argv[]) { std::shared_ptr<Bad> bp1(new Bad); std::shared_ptr<Bad> bp2 = bp1->getPtr(); std::cout << "bp2.use_count: " << bp2.use_count() << std::endl; return 0; }
程序执行结果为:
Bad() bp2.use_count: 1 ~Bad() ~Bad() a(822,0x7fff737b5300) malloc: *** error for object 0x7fe74bc04b50: pointer being freed was not allocated
我们可以看到,对象只构造了一次,但却析构了两次。并且在增加一个指向的时候 shared_ptr的计数并没有增加。也就是说,这个时候 bp1 和 bp2 都认为只有自己才是这个智能指针的拥有者。其实也就是说,这里建了两个智能指针同时处理 this 指针。每个智能指针在计数为0的时候都会调用一次Bad对象的析构函数。所以会出问题。
其实现在问题就变成了,如何在对象中获得一个指向当前对象的 shared_ptr 对象。如果我们能够做到这个,那么直接将这个shared_ptr 对象返回,就不会造成新建 shared_ptr 的问题了。
下面来看看 enable_shared_from_this;
enable_shared_from_this 是一个以其派生类为模板类型实参的基类模板,继承它,派生类的this指针就能变成一个 shared_ptr。先来看下面一份代码:
#include <iostream> #include <memory> class Good: public std::enable_shared_from_this<Good>{ public: Good() { std::cout << "Good()" << std::endl; } ~Good() { std::cout << "~Good()" << std::endl; } std::shared_ptr<Good> getPtr() { return shared_from_this(); } }; int main(int argc, char const *argv[]) { std::shared_ptr<Good> bp1(new Good); std::shared_ptr<Good> bp2 = bp1->getPtr(); std::cout << "bp2.use_count: " << bp2.use_count() << std::endl; return 0; }
执行结果:
Good() bp2.use_count: 2 ~Good()
解决问题了。
那么下面就来看看 enable_shared_from_this 到底是怎么工作的:这是它的实现源码:
template<class T> class enable_shared_from_this { protected: enable_shared_from_this() BOOST_NOEXCEPT { } enable_shared_from_this(enable_shared_from_this const &) BOOST_NOEXCEPT { } enable_shared_from_this & operator=(enable_shared_from_this const &) BOOST_NOEXCEPT { return *this; } ~enable_shared_from_this() BOOST_NOEXCEPT // ~weak_ptr<T> newer throws, so this call also must not throw { } public: shared_ptr<T> shared_from_this() { shared_ptr<T> p( weak_this_ ); BOOST_ASSERT( p.get() == this ); return p; } shared_ptr<T const> shared_from_this() const { shared_ptr<T const> p( weak_this_ ); BOOST_ASSERT( p.get() == this ); return p; } public: // actually private, but avoids compiler template friendship issues // Note: invoked automatically by shared_ptr; do not call template<class X, class Y> void _internal_accept_owner( shared_ptr<X> const * ppx, Y * py ) const { if( weak_this_.expired() ) { weak_this_ = shared_ptr<T>( *ppx, py ); } } private: mutable weak_ptr<T> weak_this_; };
我们重点看 shared_from_this 的实现。因为我们就是用它来返回一个指向 this 的智能指针对象的。我们可以看到,这个函数使用一个 weak_ptr 对象来构造一个 shared_ptr 对象,然后将其返回。注意这个 weak_ptr 是实例对象的一个成员,所以对于一个对象来说它一直是同一个,每次在调用 shared_from_this 的时候,就会根据 weak_ptr 来构造一个临时shared_ptr对象。
也许看到这里会产生疑问,这里的 shared_ptr 也是一个临时对象,和前面有什么区别?还有,为什么enable_shared_from_this 不直接保存一个 shared_ptr 成员?
首先对于第一个问题,这里的每一个shared_ptr都是根据 weak_ptr 来构造的,而每次构造shared_ptr的时候使用的参数是一样的。所以这里根据相同的weak_ptr来构造多个临时 shared_ptr 等价于用一个shared_ptr进行拷贝。
我们首先要理解,类对象肯定是外部函数通过某种机制分配的,并且如果要使用 shared_ptr 来进行管理的话,那么必须在分配完成后立即交给 shared_ptr。然后在其他地方需要用到这个对象的话也应该使用这个 shared_ptr 作为参数进行拷贝构造或赋值。而我们使用一个 weak_ptr 来进行构造 shared_ptr,作用是一样的(如果理解weak_ptr的工作原理,这个就不难理解了, weak_ptr本身就是为了协同 shared_ptr 的工作而生的)。
那么enable_shared_from_this为什么不直接保存一个 shared_ptr 成员呢?假设我再类里面储存了一个指向自身的 shared_ptr,那么这个shared_ptr的计数最少都会是1,也就是说,这个对象永远不能析构,所以这样做是错误的。而 weak_ptr 不会引起 shared_ptr 计数的增加。
enable_shared_from_this 的成员变量在enable_shared_from_this构造的时候是没有指向任何对象的,在第一次调用 shared_ptr 的时候,由 shared_ptr 调用 boost::detail::sp_enable_shared_from_this 然后调用enable_shared_from_this 的 _internal_accept_owner来对其进行初始化。看下面的过程就明白了。
shared_ptr的构造:
template<class Y> explicit shared_ptr( Y * p ): px( p ), pn( p ) { boost::detail::sp_enable_shared_from_this( this, p, p ); }
这里使用了 boost::detail::sp_enable_shared_from_this
template< class X, class Y, class T > inline void sp_enable_shared_from_this( boost::shared_ptr<X> const * ppx, Y const * py, boost::enable_shared_from_this< T > const * pe ) { if( pe != 0 ) { pe->_internal_accept_owner( ppx, const_cast< Y* >( py ) ); } }
而在这里面又调用 enable_shared_from_this 的 _internal_accept_owner
template<class X, class Y> void _internal_accept_owner( shared_ptr<X> const * ppx, Y * py ) const { if( weak_this_.expired() ) // 成员函数expired()的功能等价于use_count()==0 { weak_this_ = shared_ptr<T>( *ppx, py ); } }
从这个构造过程可以看出,当 weak_ptr 第一次指向一个对象后,它就担起了观察者的角色,一直观察着同一个对象。
从上面的介绍也可以看出,我们不能对一个普通对象去获取shared_ptr,比如:
Good g;
std::shared_ptr<Good> gp = g.shared_from_this();
这样会产生错误,因为 g 并不在堆而在栈区,然后shared_ptr试图去删除一个栈区上构造的对象,就会产生未定义行为。
另外一个要注意的就是,shared_from_this 不能在构造函数调用,因为在构造一个对象的时候,它还没被交给 shared_ptr 接管。