關於std::enable_shared_from_this 的繼承和 shared_from_this呼叫崩潰的解析
enable_shared_from_this的由來
在智慧指標的使用過程中我們會遇到這樣一種情況,我們在類的成員函式呼叫某一個函式,而該函式需要傳遞一個當前物件的智慧指標作為引數時,我們需要能夠在成員函式中獲得自己的智慧指標。在多執行緒程式設計中也存在這樣的應用,如果我們的執行緒函式繫結的是一個類成員函式,我們通過可以把該物件的智慧指標作為引數傳遞到執行緒函式中,這種做法是人為的增加了物件的引用計數,延長物件的生命週期,防止執行緒函式在執行的時候物件被釋放而引發記憶體錯誤。總之就是我們在實際的編碼中會存在各種各樣的應用。我們不能人為地通過this來構造一個當前物件的shared_ptr指標,如下錯誤的做法
void Test(std::shared_ptr<TestClass> tt)
{
}
class TestClass
{
public:
TestClass()
{
}
~TestClass()
{
}
void TestPtr()
{
std::shared_ptr<TestClass> tt = std::shared_ptr<TestClass>(this);
Test(tt);
}
};
在TestPtr()函式中通過this構造出shared_ptr,就相當於把自己的的控制權交給了這個臨時變數tt,一旦tt超出作用域就會釋放,導致該物件也被釋放。這是一個致命的錯誤。
為了解決這個問題,在c++11中提供了enable_shared_from_this這個模板類(boost庫很早就提供了這個模板類),自己的物件繼承自enable_shared_from_this。enable_shared_from_this提供了一個shared_from_this()的方法返回自己的智慧指標。與上面錯誤的例子區別在於,shared_from_this會增加該物件的引用計數,而不是重新建立一個臨時的shared_ptr來管理。看下面具體的例子:
void Test(std::shared_ptr<TestClass> tt) { } class TestClass : public std::enable_shared_from_this<TestClass> { public: TestClass() { } ~TestClass() { } void TestPtr() { std::shared_ptr<TestClass> tt = shared_from_this(); Test(tt); } }; int main() { std::shared_ptr<TestClass>t(new TestClass()); t->TestPtr(); }
這個時候程式是執行正常。
shared_from_this函式的坑
shared_from_this的出現確實能夠解決我們編碼中所遇到的問題,但是它的坑也是比較多的。我們先來看看enable_shared_from_this這個物件
template<class _Ty> class enable_shared_from_this
{ // provide member functions that create shared_ptr to this
public:
typedef _Ty _EStype;
shared_ptr<_Ty> shared_from_this()
{ // return shared_ptr
return (shared_ptr<_Ty>(_Wptr));
}
shared_ptr<const _Ty> shared_from_this() const
{ // return shared_ptr
return (shared_ptr<const _Ty>(_Wptr));
}
protected:
enable_shared_from_this()
{ // construct (do nothing)
}
enable_shared_from_this(const enable_shared_from_this&)
{ // construct (do nothing)
}
enable_shared_from_this& operator=(const enable_shared_from_this&)
{ // assign (do nothing)
return (*this);
}
~enable_shared_from_this()
{ // destroy (do nothing)
}
private:
template<class _Ty1,
class _Ty2>
friend void _Do_enable(
_Ty1 *,
enable_shared_from_this<_Ty2>*,
_Ref_count_base *);
mutable weak_ptr<_Ty> _Wptr;
};
template<class _Ty1,
class _Ty2>
inline void _Do_enable(
_Ty1 *_Ptr,
enable_shared_from_this<_Ty2> *_Es,
_Ref_count_base *_Refptr)
{ // reset internal weak pointer
_Es->_Wptr._Resetw(_Ptr, _Refptr);
}
這是標準庫的原始碼,我們看到在enable_shared_from_this內部儲存了一個weak_ptr。shared_from_this函式就是通過這個weak_ptr得到了。但是另外一點,我們可以看到在enable_shared_from_this的建構函式中並沒有對這個weak_ptr進行初始化。這就是為什麼我們不能在建構函式呼叫shared_from_this()的原因,因為其內部的weak_ptr並沒有初始化。所以會產生錯誤。
在實際的程式設計中如果我們需要在物件初始化中用到自己的shared_ptr。可以單獨將初始化操作放到一個獨立的init函式中,這時候再呼叫shared_from_this()是沒有問題的(但還是有點問題,下面會講到)
熟悉weak_ptr的同學可能知道,我們在使用weak_ptr前,需要用一個shared_ptr來對其進行初始化。對weak_ptr初始化是要能獲取到當前物件的引用計數物件,而引用計數物件可以通過shared_ptr物件獲取到。當然我們同樣可以用一個已經初始化過的weak_ptr來初始化另一個weak_ptr,因為已初始化的weak_ptr也可能獲取到物件的引用計數。
enable_shared_from_this內部的weak_ptr是通過_Do_enable函式初始化的。而_Do_enable函式實在shared_ptr的建構函式中呼叫的,這是至關重要的一個環節。正因為如此我們在呼叫shared_from_this之前請確保程式已經顯式地建立了shared_ptr物件,要不然enable_shared_from_this內部的weak_ptr始終是無效。
下面具體舉例說明的:
class TestClass : public std::enable_shared_from_this<TestClass>
{
public:
TestClass()
{
}
~TestClass()
{
//TestClassPtr tt = shared_from_this();
}
void TestPtr()
{
std::shared_ptr<TestClass> tt = shared_from_this();
Test(tt);
}
};
int main()
{
TestClass t;
t.TestPtr(); //shared_from_this()錯誤
TestClass* t1(new TestClass());
t1->TestPtr();//shared_from_this()錯誤
std::shared_ptr<TestClass> t2(new TestClass());
t2->TestPtr(); //正確,已提前建立了shared_ptr
}
同理在解構函式中也不能呼叫shared_from_this()。
在析構時,引用計數已經變為零,weak_ptr已經相當於指向的是一個無效的物件,這是不能通過此無效的weak_ptr構造shared_ptr。