如何安全的傳遞 this 指標給其他函式

還記得在觀察者模式一文中,IObserver::Update 最後修改為傳 ISubject*,因此 CTSMCStockSubject 在 Notify 的實作是傳入 this 指標,程式碼如下:

class IObserver
{
public:
	virtual void Update(ISubject* pSubject) = 0;
};
class CTSMCStockSubject : public ISubject
{
public:
	virtual void Notify() override
	{
		for (auto& ob : m_observers)
		{
			ob->Update(this); // 傳入 this 指標
		}
	}
};

有沒有想過如果 CTAViewObserver 使用拉 (pull) 資料的方式,因此先將 ISubject* 儲存起來會有什麼問題呢?

class CTAViewObserver : public IObserver
{
public:
	virtual void Update(ISubject* pSubject) override
	{
		// 將 pSubject 儲存,稍後有時間再回主題類別拉 (pull) 資料
		m_mapStockID2Subject[pSubject->GetStockID()] = pSubject;
	}
};

答案就是,如果主題的生命週期短於觀察者,那麼到時觀察者存取 ISubject* 會導致未定義行為,因此我們會希望 Update 可以改為傳 std::shared_ptr< ISubject >,程式修改如下:

class IObserver
{
public:
	virtual void Update(const std::shared_ptr<ISubject>& pSubject) = 0;
};

那麼現在 CTSMCStockSubject::Notify 要怎麼呼叫 Update 呢?也就是怎麼將 this 指標轉為 std::shared_ptr<ISubject> 呢?

這時就是 std::enable_shared_from_this 派上用場的地方了,首先 CTSMCStockSubject 要繼承至 std::enable_shared_from_this<CTSMCStockSubject>,再來呼叫 Update 的地方修改為傳入 shared_from_this(),程式碼如下:

class CTSMCStockSubject : public ISubject, public std::enable_shared_from_this<CTSMCStockSubject>
{
public:
	virtual void Notify() override
	{
		for (auto& ob : m_observers)
		{
			ob->Update(shared_from_this());
		}
	}
};

最後要注意的地方是,現在 CTSMCStockSubject 必須要使用 std::shared_ptr 的方式才行。

int main()
{
	auto pTAViewObserver = std::make_shared<CTAViewObserver>();
	auto pTSMCStockSubject = std::make_shared<CTSMCStockSubject>();
	pTSMCStockSubject->Subscribe(pTAViewObserver);
	pTSMCStockSubject->Notify();
	pTSMCStockSubject->Unsubscribe(pTAViewObserver);
	return 0;
}

P.S. std::enable_shared_from_this 是個基底類別模板,模板參數是繼承的類別名稱,這個設計模式叫做 The Curiously Recurring Template Pattern (CRTP)