沒有銀子彈......而shared_ptr
當然不是其中之一。
我的第一個問題是:你是否需要所有這些共享指針?
避免循環引用的最佳方法是定義每個對象的生命週期策略並確保它們是兼容的。這可以很容易地證明:
- 你給我一個參考,我希望對象住整個函數調用,但沒有更多的
- 你給我一個
unique_ptr
,我現在的對象負責
- 你給我一個
shared_ptr
,我希望能夠將手柄保持對象自己沒有你
不利影響現在,有罕見確實需要使用shared_ptr
的情況。緩存指示讓我認爲它可能是你的情況,至少對於一些使用。
在這種情況下,您可以(至少非正式地)實施分層方法。
- 定義數量的層,從
0
(基),以無限
- 每個類型的對象是歸因於一個層時,幾種類型可以共享相同的層
A
類型的對象可能只持有shared_ptr
到B
如果類型的對象,且僅當Layer(A) > Layer(B)
請注意,我們明確禁止兄弟關係。有了這個方案,就不會形成任何引用的循環。事實上,我們獲得了一個DAG(有向無環圖)。
現在,當創建一個類型時,它必須被賦予一個圖層編號,並且必須記錄下來(最好在代碼中)。
的對象可能變層的,但是:
- 如果層數減少,那麼你必須重新審視其持有的引用(容易)
- 如果層數的增加,則必須重新檢查所有對它的引用(硬)
注意:按照慣例,不能容納任何引用的對象的類型通常在圖層0
中。注2:我首先在Herb Sutter的一篇文章中偶然發現了這個約定,並將它應用於Mutexes並試圖防止死鎖。這是對當前問題的適應。
這可以更自動,只要你準備工作,你現有的代碼庫來執行位(由編譯器)。
我們創建一個新SharedPtr
類知道我們分層方案:
template <typename T>
constexpr unsigned getLayer(T const&) { return T::Layer; }
template <typename T, unsigned L>
class SharedPtrImpl {
public:
explicit SharedPtrImpl(T* t): _p(t)
{
static_assert(L > getLayer(std::declval<T>()), "Layering Violation");
}
T* get() const { return _p.get(); }
T& operator*() const { return *this->get(); }
T* operator->() const { return this->get(); }
private:
std::shared_ptr<T> _p;
};
可以在這樣一個SharedPtr
舉行靜態給出了層,我們用一個基類來幫助我們每種類型:
template <unsigned L>
struct LayerMember {
static unsigned const Layer = L;
template <typename T>
using SharedPtr<T> = SharedPtrImpl<T, L>;
};
而現在,我們可以很容易地使用它:
class Foo: public LayerMember<3> {
public:
private:
SharedPtr<Bar> _bar; // statically checked!
};
然而,這種編碼方法有一點涉及,我認爲約定可能已經足夠;)