1. 程式人生 > >關於std::enable_shared_from_this 的繼承和 shared_from_this呼叫崩潰的解析

關於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。