C++:为什么unique_ptr的Deleter是模板类型参数,而shared_ptr的Deleter不是?
template class T, class D = default_delete T class unique_ptr { public: unique_ptr (pointer p, typename conditional is_reference D ::value,D,const D del) noexcept; template class T class shared_ptr { public: template class U, class D shared_ptr (U* p, D del); };
上面的代码中能看到unique_ptr的第二个模板类型参数是Deleter,而shared_ptr的Delete则只是构造函数参数的一部分,并不是shared_ptr的类型的一部分。
为什么会有这个区别呢?
答案是效率。unique_ptr的设计目标之一是尽可能的高效,如果用户不指定Deleter,就要像原生指针一样高效。
Deleter作为对象的成员一般会有哪些额外开销?
通常要存起来,多占用空间。 调用时可能会有一次额外的跳转(相比delete或delete[])。shared_ptr总是要分配一个ControlBlock的,多加一个Deleter的空间开销也不大,第一条pass;shared_ptr在析构时要先原子减RefCount,如果WeakCount也为0还要再析构ControlBlock,那么调用Deleter析构持有的对象时多一次跳转也不算什么,第二条pass。
既然shared_ptr并不担心Deleter带来的额外开销,同时把Deleter作为模板类型的一部分还会导致使用上变复杂,那么它只把Deleter作为构造函数的类型就是显然的事情了。
而unique_ptr采用了“空基类”的技巧,将Deleter作为基类,在用户不指定Deleter时根本不占空间,第一条pass;用户不指定Deleter时默认的Deleter会是default_delete,它的operator()在类的定义内,会被inline掉,这样调用Deleter时也就没有额外的开销了,第二条pass。
因此unique_ptr通过上面两个技巧,成功的消除了默认Deleter可能带来的额外开销,保证了与原生指针完全相同的性能。代价就是Deleter需要是模板类型的一部分。
Why does unique_ptr take two template parameters when shared_ptr only takes one? Why does unique_ptr have the deleter as a type parameter while shared_ptr doesnt?unique_ptr是如何使用空基类技巧的
我们参考clang的实现来学习一下unique_ptr使用的技巧。
template class _Tp, class _Dp = default_delete _Tp class unique_ptr public: typedef _Tp element_type; typedef _Dp deleter_type; typedef typename __pointer_type _Tp, deleter_type ::type pointer; private: __compressed_pair pointer, deleter_type __ptr_; };
忽略掉unique_ptr中的各种成员函数,我们看到它只有一个成员变量__ptr__,类型是__compressed_pair pointer, deleter_type 。我们看看它是什么,是怎么省掉了Deleter的空间的。
template class _T1, class _T2 class __compressed_pair : private __libcpp_compressed_pair_imp _T1, _T2 { };
__compressed_pair没有任何的成员变量,就说明它的秘密藏在了它的基类中,我们继续看。
template class _T1, class _T2, unsigned = __libcpp_compressed_pair_switch _T1, _T2 ::value class __libcpp_compressed_pair_imp;
__libcpp_compressed_pair_imp有三个模板类型参数,前两个是传入的_T1和_T2,第三个参数是一个无符号整数,它是什么?我们往下看,看到了它的若干个特化版本:
template class _T1, class _T2 class __libcpp_compressed_pair_imp _T1, _T2, 0 private: _T1 __first_; _T2 __second_; template class _T1, class _T2 class __libcpp_compressed_pair_imp _T1, _T2, 1 : private _T1 private: _T2 __second_; template class _T1, class _T2 class __libcpp_compressed_pair_imp _T1, _T2, 2 : private _T2 private: _T1 __first_; template class _T1, class _T2 class __libcpp_compressed_pair_imp _T1, _T2, 3 : private _T1, private _T2 };
看起来第三个参数有4种取值,分别是:
0: 没有基类,两个成员变量。 1: 有一个基类_T1,和一个_T2类型的成员变量。 2: 有一个基类_T2,和一个_T1类型的成员变量。 3: 有两个基类_T1和_T2,没有成员变量。__compressed_pair继承自__libcpp_compressed_pair_imp _T1, _T2 ,没有指定第三个参数的值,那么这个值应该来自__libcpp_compressed_pair_switch _T1, _T2 ::value。我们看一下__libcpp_compressed_pair_switch是什么:
template class _T1, class _T2, bool = is_same typename remove_cv _T1 ::type, typename remove_cv _T2 ::type ::value, bool = is_empty _T1 ::value !__libcpp_is_final _T1 ::value, bool = is_empty _T2 ::value !__libcpp_is_final _T2 ::value struct __libcpp_compressed_pair_switch; template class _T1, class _T2, bool IsSame struct __libcpp_compressed_pair_switch _T1, _T2, IsSame, false, false {enum {value = 0};}; template class _T1, class _T2, bool IsSame struct __libcpp_compressed_pair_switch _T1, _T2, IsSame, true, false {enum {value = 1};}; template class _T1, class _T2, bool IsSame struct __libcpp_compressed_pair_switch _T1, _T2, IsSame, false, true {enum {value = 2};}; template class _T1, class _T2 struct __libcpp_compressed_pair_switch _T1, _T2, false, true, true {enum {value = 3};}; template class _T1, class _T2 struct __libcpp_compressed_pair_switch _T1, _T2, true, true, true {enum {value = 1};};
__libcpp_compressed_pair_switch的三个bool模板参数的含义是:
可以看到,在_T1和_T2不同时,它们中的空类型就会被当作__compressed_pair的基类,就会利用到C++中的“空基类优化“。
那么在unique_ptr中,_T1和_T2都是什么呢?看前面的代码,_T1就是__pointer_type _Tp, deleter_type ::type,而_T2则是Deleter,在默认情况下是default_delete _Tp 。
我们先看__pointer_type是什么:
namespace __pointer_type_imp template class _Tp, class _Dp, bool = __has_pointer_type _Dp ::value struct __pointer_type typedef typename _Dp::pointer type; template class _Tp, class _Dp struct __pointer_type _Tp, _Dp, false typedef _Tp* type; } // __pointer_type_imp template class _Tp, class _Dp struct __pointer_type typedef typename __pointer_type_imp::__pointer_type _Tp, typename remove_reference _Dp ::type ::type type; };
可以看到__pointer_type _Tp, deleter_type ::type就是__pointer_type_imp::__pointer_type _Tp, typename remove_reference _Dp ::type ::type。这里我们看到了__has_pointer_type,它是什么?
namespace __has_pointer_type_imp template class _Up static __two __test(...); template class _Up static char __test(typename _Up::pointer* = 0); }
简单来说__has_pointer_type就是:如果_Up有一个内部类型pointer,即_Up::pointer是一个类型,那么__has_pointer_type就返回true,例如pointer_traits::pointer,否则返回false。
大多数场景下_Dp不会是pointer_traits,因此__has_pointer_type就是false,__pointer_type _Tp, deleter_type ::type就是_Tp*,我们终于看到熟悉的原生指针了!
_T1是什么我们已经清楚了,就是_Tp*,它不会是空基类。那么_T2呢?我们看default_delete _Tp :
template class _Tp struct default_delete template class _Up default_delete(const default_delete _Up , typename enable_if is_convertible _Up*, _Tp* ::value ::type* = 0) _NOEXCEPT {} void operator() (_Tp* __ptr) const _NOEXCEPT static_assert(sizeof(_Tp) 0, "default_delete can not delete incomplete type"); static_assert(!is_void _Tp ::value, "default_delete can not delete incomplete type"); delete __ptr; };
我们看到default_delete符合上面说的空类型的几个要求,因此_T2就是空类型,也是__compressed_pair的基类,在”空基类优化“后,_T2就完全不占空间了,只占一个原生指针的空间。
而且default_delete::operator()是定义在default_delete内部的,默认是inline的,它在调用上的开销也被省掉了!
__libcpp_compressed_pair_switch在_T1和_T2类型相同,且都是空类型时,为什么只继承自_T1,而把_T2作为成员变量的类型? unique_ptr与pointer_traits是如何交互的?
C++模板和泛型编程详解 C++中的模板和泛型编程是非常重要的概念。模板是一种将数据类型作为参数的通用程序设计方法。它们允许开发人员编写可以处理各种数据类型的代码,而无需为每种数据类型编写不同的代码。下面介绍了一些关于C++中模板和泛型编程的重要知识点
【C++初阶】十三、模板进阶(总) 一、非类型模板参数 二、模板的特化 2.1 模板特化概念 2.2 函数模板特化 2.3 类模板特化 2.3.1 全特化 2.3.2 偏特化 三、模板分离编译 四、模板总结(优缺点)
【C++初阶】四、模板初阶 一、泛型编程 二、函数模板 2.1 函数模板概念 2.1 函数模板格式 2.3 函数模板的原理 2.4 函数模板的实例化 2.5 模板参数的匹配原则 2.6 函数模板不支持定义和声明分离 三、类模板 3.1 类模板的定义格式 3.2 类模板的实例化
C++【模板初阶】 早在北宋年间,中国的毕昇就已经发明了泥活字,标志着四大发明之一的活字印刷术正式诞生,从此文化传播取得了革命性突破,各种文学作品得以走进千家万户。倘若这项技术还没有被发明,那么恐怕我们现在的书本都还得靠逐字手抄传播,效率是非常低的 我们的程序也是如此,很多需要频繁使用的函数每次都得手动写,这可难不倒程序员,于是在上世纪80年代末,范型编程思想正式诞生,它就像是印刷文字的模具,将程序主体刻在其中,需要使用时让编译器根据参数类型生成即可,这就是我们今天的主角模板
安菲拉尔 哈尔滨工业大学微电子学硕士,主攻方向为分布式存储与高性能服务器编程,目前就职于阿里云表格存储团队,负责后端开发。
相关文章
- 【C++保姆级入门】if分支语句详解
- C++标准模板库集合类与映射类总结
- C++标准模板库Stand Template Library(STL)简介与STL string类
- C++ 宏和模板简介
- C++的标准模板库STL中实现的数据结构之顺序表vector的分析与使用
- C++:static【规定此全局变量只在当前模块(文件)中可见】、const【只读的全局变量,其值不可修改,只在声明中使用】、static const【既是只读的,又是只在当前模块中可见的】
- C++-STL-组件(一)-容器01:string【深浅拷贝、模拟实现、写时拷贝】
- 模拟实现C++中的string类(详细解析)
- C++初阶-模板初阶
- C/C++标准库和标准模板库总结
- [native] 00 - Emscripten: js --> asm.js --> WebAssembly --> C/C++
- 【Chrome】如何在C++中增加给JavaScript调用的API
- C++拾遗--类成员指针
- C++函数模板
- c++友元函数
- c++友元函数
- (vector&C++)(三、删除)erase、pop_back和clear
- VC++下使用md5函数
- C++如何判断素数
- 使用C++和Directx开发游戏GUI(四)
- 演练:创建和使用静态库 (C++)
- 2009-08-10 10:38 C++中模板使用介绍
- 开源免费的C/C++网络库(c/c++ sockets library)
- C++ DLL 模板 .
- VS2010 C++环境下DLL和LIB文件目录及名称修改
- C++11 可变模板参数
- C++模板编程之变长参数模板
- qt中使用C++thread
- C++函数包装模板function
- C++:函数重载和模板函数之间的问题 | 完美转发的概念
- C++:函数模板(1)—— 泛化版本、部分特化版本、完全特化版本
- Visual c++例子,可不使用常规的对话框资源模板的情况下,动态创建对话框的方法