首先要說明的一個問題是:如何安全地將this指針返回給調用者。通常來講,咱們不能直接將this指針返回。想象這樣的狀況,該函數將this指針返回到外部某個變量保存,而後這個對象自身已經析構了,但外部變量並不知道,此時若是外部變量使用這個指針,就會使得程序崩潰。html
使用智能指針shared_ptr看起來是個不錯的解決方法。但問題是如何去使用它呢?咱們來看以下代碼:ios
#include <iostream> #include <boost/shared_ptr.hpp> class Test { public: //析構函數 ~Test() { std::cout << "Test Destructor." << std::endl; } //獲取指向當前對象的指針 boost::shared_ptr<Test> GetObject() { boost::shared_ptr<Test> pTest(this); return pTest; } }; int main(int argc, char *argv[]) { { boost::shared_ptr<Test> p( new Test( )); std::cout << "q.use_count(): " << q.use_count() << std::endl; boost::shared_ptr<Test> q = p->GetObject(); } return 0; }
運行後,程序輸出:git
Test Destructor.
github
q.use_count(): 1安全
Test Destructor.
函數
能夠看到,對象只構造了一次,但卻析構了兩次。而且在增長一個指向的時候,shared_ptr的計數並無增長。也就是說,這個時候,p和q都認爲本身是Test指針的惟一擁有者,這兩個shared_ptr在計數爲0的時候,都會調用一次Test對象的析構函數,因此會出問題。post
那麼爲何會這樣呢?給一個shared_ptr<Test>傳遞一個this指針難道不能引發shared_ptr<Test>的計數嗎?this
答案是:對的,shared_ptr<Test>根本認不得你傳進來的指針變量是否是以前已經傳過。url
看這樣的代碼:spa
int main() { Test* test = new Test(); shared_ptr<Test> p(test); shared_ptr<Test> q(test); std::cout << "p.use_count(): " << p.use_count() << std::endl; std::cout << "q.use_count(): " << q.use_count() << std::endl; return 0; }
運行後,程序輸出:
p.use_count(): 1
q.use_count(): 1
Test Destructor.
Test Destructor.
也證實了剛剛的論述:shared_ptr<Test>根本認不得你傳進來的指針變量是否是以前已經傳過。
事實上,類對象是由外部函數經過某種機制分配的,並且一經分配當即交給 shared_ptr管理,並且之後凡是須要共享使用類對象的地方,必須使用這個 shared_ptr看成右值來構造產生或者拷貝產生(shared_ptr類中定義了賦值運算符函數和拷貝構造函數)另外一個shared_ptr ,從而達到共享使用的目的。
解釋了上述現象後,如今的問題就變爲了:如何在類對象(Test)內部中得到一個指向當前對象的shared_ptr 對象?若是咱們可以作到這一點,直接將這個shared_ptr對象返回,就不會形成新建的shared_ptr的問題了。
下面來看看enable_shared_from_this類的威力。
enable_shared_from_this 是一個以其派生類爲模板類型參數的基類模板,繼承它,派生類的this指針就能變成一個 shared_ptr。
有以下代碼:
#include <iostream> #include <boost/enable_shared_from_this.hpp> #include <boost/shared_ptr.hpp> class Test : public boost::enable_shared_from_this<Test> //改進1 { public: //析構函數 ~Test() { std::cout << "Test Destructor." << std::endl; } //獲取指向當前對象的指針 boost::shared_ptr<Test> GetObject() { return shared_from_this(); //改進2 } }; int main(int argc, char *argv[]) { { boost::shared_ptr<Test> p( new Test( )); std::cout << "q.use_count(): " << q.use_count() << std::endl; boost::shared_ptr<Test> q = p->GetObject(); } return 0; }
運行後,程序輸出:
Test Destructor.
q.use_count(): 2;
能夠看到,問題解決了!
接着來看看enable_shared_from_this 是如何工做的,如下是它的源碼:
template<class T> class enable_shared_from_this { protected: BOOST_CONSTEXPR enable_shared_from_this() BOOST_SP_NOEXCEPT { } BOOST_CONSTEXPR enable_shared_from_this(enable_shared_from_this const &) BOOST_SP_NOEXCEPT { } enable_shared_from_this & operator=(enable_shared_from_this const &) BOOST_SP_NOEXCEPT { return *this; } ~enable_shared_from_this() BOOST_SP_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; } weak_ptr<T> weak_from_this() BOOST_SP_NOEXCEPT { return weak_this_; } weak_ptr<T const> weak_from_this() const BOOST_SP_NOEXCEPT { return weak_this_; } 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 BOOST_SP_NOEXCEPT { if( weak_this_.expired() ) { weak_this_ = shared_ptr<T>( *ppx, py ); } } private: mutable weak_ptr<T> weak_this_; }; } // namespace boost #endif // #ifndef BOOST_SMART_PTR_ENABLE_SHARED_FROM_THIS_HPP_INCLUDED
標黃部分是shared_from_this()函數的實現。能夠看到,這個函數使用一個weak_ptr對象(weak_this_)來構造一個 shared_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來作拷貝。(PS:在shared_ptr類中,是有使用weak_ptr對象來構造shared_ptr對象的構造函數的:
template<class Y>
explicit shared_ptr( weak_ptr<Y> const & r ): pn( r.pn )
)
對於第二個問題,假設我在類裏儲存了一個指向自身的shared_ptr,那麼這個 shared_ptr的計數最少都會是1,也就是說,這個對象將永遠不能析構,因此這種作法是不可取的。
在enable_shared_from_this類中,沒有看到給成員變量weak_this_初始化賦值的地方,那到底是如何保證weak_this_擁有着Test類對象的指針呢?
首先咱們生成類T時,會依次調用enable_shared_from_this類的構造函數(定義爲protected),以及類Test的構造函數。在調用enable_shared_from_this的構造函數時,會初始化定義在enable_shared_from_this中的私有成員變量weak_this_(調用其默認構造函數),這時的weak_this_是無效的(或者說不指向任何對象)。
接着,當外部程序把指向類Test對象的指針做爲初始化參數來初始化一個shared_ptr(boost::shared_ptr<Test> p( new Test( ));)。
如今來看看 shared_ptr是如何初始化的,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() ) { weak_this_ = shared_ptr<T>( *ppx, py ); } }
而在這裏,對enable_shared_from_this 類的成員weak_this_進行拷貝賦值,使得weak_this_做爲類對象 shared_ptr 的一個觀察者。
這時,當類對象自己須要自身的shared_ptr時,就能夠從這個weak_ptr來生成一個了:
shared_ptr<T> shared_from_this() { shared_ptr<T> p( weak_this_ ); BOOST_ASSERT( p.get() == this ); return p; }
以上。
從上面的說明來看,須要當心的是shared_from_this()僅在shared_ptr<T>的構造函數被調用以後才能使用,緣由是enable_shared_from_this::weak_this_並不在構造函數中設置,而是在shared_ptr<T>的構造函數中設置。
因此,以下代碼是錯誤的:
class D:public boost::enable_shared_from_this<D> { public: D() { boost::shared_ptr<D> p=shared_from_this(); } };
緣由是在D的構造函數中雖然能夠保證enable_shared_from_this<D>的構造函數被調用,但weak_this_是無效的(還還沒被接管)。
以下代碼也是錯誤的:
class D:public boost::enable_shared_from_this<D> { public: void func() { boost::shared_ptr<D> p=shared_from_this(); } }; void main() { D d; d.func(); }
緣由同上。
總結爲:不要試圖對一個沒有被shared_ptr接管的類對象調用shared_from_this(),否則會產生未定義行爲的錯誤。
參考:
Boost 庫 Enable_shared_from_this 實現原理分析
如何用enable_shared_from_this 來獲得指向自身的shared_ptr及對enable_shared_from_this 的理解