上面的代码中能看到<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<pointer, deleter_type></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<_T1, _T2></code>,没有指定第三个参数的值,那么这个值应该来自<code>__libcpp_compressed_pair_switch<_T1, _T2>::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<_Tp, deleter_type>::type</code>,而<code>_T2</code>则是Deleter,在默认情况下是<code>default_delete<_Tp></code>。
我们先看<code>__pointer_type</code>是什么:
可以看到<code>__pointer_type<_Tp, deleter_type>::type</code>就是<code>__pointer_type_imp::__pointer_type<_Tp, typename remove_reference<_Dp>::type>::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<_Tp, deleter_type>::type</code>就是<code>_Tp*</code>,我们终于看到熟悉的原生指针了!
<code>_T1</code>是什么我们已经清楚了,就是<code>_Tp*</code>,它不会是空基类。那么<code>_T2</code>呢?我们看<code>default_delete<_Tp></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>是如何交互的?