2017-01-18 35 views
15

我讀過很多地方,當使用創建shared_ptr<T>時,其控制塊包含一個足夠容納T的存儲塊,然後該對象在存儲器中構建安置新。事情是這樣的:爲什麼shared_ptr使用放置新

template<typename T> 
struct shared_ptr_control_block { 
    std::atomic<long> count; 
    std::atomic<long> weak_count; 
    std::aligned_storage_t<sizeof (T), alignof (T)> storage; 
}; 

但我是一個有點困惑,爲什麼我們不能僅僅有T類型,而不是一個成員變量?爲什麼要創建原始存儲,然後使用新的位置?不能將它與T類型的普通對象組合在一起嗎?

回答

18

這是允許終身管理。

直到weak_count爲零,控制塊才被銷燬。一旦count達到零,storage對象就會被銷燬。這意味着您需要在計數達到零時直接調用storage的析構函數,並且在控制塊的析構函數中調用而不是

爲了防止控制塊的析構函數調用storage的析構函數,storage的實際類型不能爲T

如果我們只有強引用計數,那麼T會很好(並且更簡單)。


實際上,實現比這更復雜一點。請記住,可以通過分配Tnew,然後從中構建shared_ptr來構建shared_ptr。因此,實際控制塊看起來更像是:

template<typename T> 
struct shared_ptr_control_block { 
    std::atomic<long> count; 
    std::atomic<long> weak_count; 
    T* ptr; 
}; 

,什麼make_shared分配是:

template<typename T> 
struct both { 
    shared_ptr_control_block cb; 
    std::aligned_storage_t<sizeof (T), alignof (T)> storage; 
}; 

而且cb.p設置爲storage地址。在make_shared中分配both結構意味着我們得到一個內存分配,而不是兩個(並且內存分配很昂貴)。

注意:我已經簡化了:shared_ptr析構函數必須知道控制塊是否爲both的一部分(在這種情況下,內存無法在完成之前釋放),或者不是(在這種情況下,可以更早釋放)。這可能是一個簡單的布爾標誌(在這種情況下控制塊較大),或者通過在指針中使用一些備用位(這不是可移植的 - 但標準庫實現不必是可移植的)。該實現甚至可以是更多複雜,以避免在make_shared的情況下在所有處存儲指針

+0

啊,我明白了。所以如果我們只有一個引用計數(強計數),那麼'T'就足夠了,對吧? –

+0

@ZizhengTai,yes –

+0

值得一提的是,make_shared結合了控制塊和對象,以減少所需的內存分配數量。只需構造一個shared_ptr,就可以爲共享對象分配一個分配,併爲控制塊分配一個分配。 –

7

由於弱指針可能超過存儲對象,控制塊的生存期可能不得不超過存儲對象的生存期。如果託管對象是一個成員變量,它只能在控制塊被銷燬時被銷燬(或者析構函數會被調用兩次)。

即使在對象本身被破壞之後存儲仍保持分配的事實實際上可能是內存約束系統中的make_shared的缺點(儘管我不知道這是否在實踐中遇到了某些問題)。

相關問題