天天看點

C++:為什麼unique_ptr的Deleter是模闆類型參數,而shared_ptr的Deleter不是?

上面的代碼中能看到<code>unique_ptr</code>的第二個模闆類型參數是Deleter,而<code>shared_ptr</code>的Delete則隻是構造函數參數的一部分,并不是<code>shared_ptr</code>的類型的一部分。

為什麼會有這個差別呢?

答案是效率。<code>unique_ptr</code>的設計目标之一是盡可能的高效,如果使用者不指定Deleter,就要像原生指針一樣高效。

Deleter作為對象的成員一般會有哪些額外開銷?

通常要存起來,多占用空間。

調用時可能會有一次額外的跳轉(相比<code>delete</code>或<code>delete[]</code>)。

<code>shared_ptr</code>總是要配置設定一個ControlBlock的,多加一個Deleter的空間開銷也不大,第一條pass;<code>shared_ptr</code>在析構時要先原子減RefCount,如果WeakCount也為0還要再析構ControlBlock,那麼調用Deleter析構持有的對象時多一次跳轉也不算什麼,第二條pass。

既然<code>shared_ptr</code>并不擔心Deleter帶來的額外開銷,同時把Deleter作為模闆類型的一部分還會導緻使用上變複雜,那麼它隻把Deleter作為構造函數的類型就是顯然的事情了。

而<code>unique_ptr</code>采用了“空基類”的技巧,将Deleter作為基類,在使用者不指定Deleter時根本不占空間,第一條pass;使用者不指定Deleter時預設的Deleter會是<code>default_delete</code>,它的<code>operator()</code>在類的定義内,會被inline掉,這樣調用Deleter時也就沒有額外的開銷了,第二條pass。

是以<code>unique_ptr</code>通過上面兩個技巧,成功的消除了預設Deleter可能帶來的額外開銷,保證了與原生指針完全相同的性能。代價就是Deleter需要是模闆類型的一部分。

<a href="http://stackoverflow.com/questions/21355037/why-does-unique-ptr-take-two-template-parameters-when-shared-ptr-only-takes-one">Why does unique_ptr take two template parameters when shared_ptr only takes one?</a>

<a href="http://stackoverflow.com/questions/6829576/why-does-unique-ptr-have-the-deleter-as-a-type-parameter-while-shared-ptr-doesn">Why does unique_ptr have the deleter as a type parameter while shared_ptr doesn't?</a>

我們參考clang的實作來學習一下<code>unique_ptr</code>使用的技巧。

忽略掉<code>unique_ptr</code>中的各種成員函數,我們看到它隻有一個成員變量<code>__ptr__</code>,類型是<code>__compressed_pair&lt;pointer, deleter_type&gt;</code>。我們看看它是什麼,是怎麼省掉了Deleter的空間的。

<code>__compressed_pair</code>沒有任何的成員變量,就說明它的秘密藏在了它的基類中,我們繼續看。

<code>__libcpp_compressed_pair_imp</code>有三個模闆類型參數,前兩個是傳入的<code>_T1</code>和<code>_T2</code>,第三個參數是一個無符号整數,它是什麼?我們往下看,看到了它的若幹個特化版本:

看起來第三個參數有4種取值,分别是:

0: 沒有基類,兩個成員變量。

1: 有一個基類<code>_T1</code>,和一個<code>_T2</code>類型的成員變量。

2: 有一個基類<code>_T2</code>,和一個<code>_T1</code>類型的成員變量。

3: 有兩個基類<code>_T1</code>和<code>_T2</code>,沒有成員變量。

<code>__compressed_pair</code>繼承自<code>__libcpp_compressed_pair_imp&lt;_T1, _T2&gt;</code>,沒有指定第三個參數的值,那麼這個值應該來自<code>__libcpp_compressed_pair_switch&lt;_T1, _T2&gt;::value</code>。我們看一下<code>__libcpp_compressed_pair_switch</code>是什麼:

<code>__libcpp_compressed_pair_switch</code>的三個bool模闆參數的含義是:

<code>_T1</code>和<code>_T2</code>在去掉頂層的<code>const</code>和<code>volatile</code>後,是不是相同類型。

<code>_T1</code>是不是空類型。

<code>_T2</code>是不是空類型。

滿足以下條件的類型就是空類型:

不是union;

除了size為0的位域之外,沒有非static的成員變量;

沒有虛函數;

沒有虛基類;

沒有非空的基類。

可以看到,在<code>_T1</code>和<code>_T2</code>不同時,它們中的空類型就會被當作<code>__compressed_pair</code>的基類,就會利用到C++中的“空基類優化“。

那麼在<code>unique_ptr</code>中,<code>_T1</code>和<code>_T2</code>都是什麼呢?看前面的代碼,<code>_T1</code>就是<code>__pointer_type&lt;_Tp, deleter_type&gt;::type</code>,而<code>_T2</code>則是Deleter,在預設情況下是<code>default_delete&lt;_Tp&gt;</code>。

我們先看<code>__pointer_type</code>是什麼:

可以看到<code>__pointer_type&lt;_Tp, deleter_type&gt;::type</code>就是<code>__pointer_type_imp::__pointer_type&lt;_Tp, typename remove_reference&lt;_Dp&gt;::type&gt;::type</code>。這裡我們看到了<code>__has_pointer_type</code>,它是什麼?

簡單來說<code>__has_pointer_type</code>就是:如果<code>_Up</code>有一個内部類型<code>pointer</code>,即<code>_Up::pointer</code>是一個類型,那麼<code>__has_pointer_type</code>就傳回<code>true</code>,例如<code>pointer_traits::pointer</code>,否則傳回<code>false</code>。

大多數場景下<code>_Dp</code>不會是<code>pointer_traits</code>,是以<code>__has_pointer_type</code>就是<code>false</code>,<code>__pointer_type&lt;_Tp, deleter_type&gt;::type</code>就是<code>_Tp*</code>,我們終于看到熟悉的原生指針了!

<code>_T1</code>是什麼我們已經清楚了,就是<code>_Tp*</code>,它不會是空基類。那麼<code>_T2</code>呢?我們看<code>default_delete&lt;_Tp&gt;</code>:

我們看到<code>default_delete</code>符合上面說的空類型的幾個要求,是以<code>_T2</code>就是空類型,也是<code>__compressed_pair</code>的基類,在”空基類優化“後,<code>_T2</code>就完全不占空間了,隻占一個原生指針的空間。

而且<code>default_delete::operator()</code>是定義在<code>default_delete</code>内部的,預設是inline的,它在調用上的開銷也被省掉了!

<code>__libcpp_compressed_pair_switch</code>在<code>_T1</code>和<code>_T2</code>類型相同,且都是空類型時,為什麼隻繼承自<code>_T1</code>,而把<code>_T2</code>作為成員變量的類型?

<code>unique_ptr</code>與<code>pointer_traits</code>是如何互動的?

繼續閱讀