STL源码剖析——Iterators与Traits编程#5 __type_traits
上节给出了iterator_traits以及用到traits机制的部分函数的完整代码,可以看到traits机制能够提取迭代器的特性从而调用不同的函数,实现效率的最大化。显然这么好的机制不应该仅局限于在STL里面使用,在前某一节中我们也有说到,traits机制能够萃取类的特性,而这个类分为两个类别,一是迭代器类,二普通类,迭代器类我们已经有所了解了,那么本节就来学习负责萃取普通类型特性的__type_traits吧。
于普通类型而言,我们所关注的特性是指:这个类型是否具备non-trivial defalt ctor?是否具备non-trivial copy ctor?是否具备non-trivial assignment ctor?是否具备non-trivial dtor?在关注__typr_traits之前,我们很有必要来讨论一下上述的几个词组是什么意思。其中ctor为构造函数,那么copy ctor就是拷贝构造函数,assignment ctro为赋值构造函数,defalt ctor为默认构造函数。而trivial的意思为琐碎的、无意义的。那么判断这4个构造函数是否有意义的条件是:如果至少满足一下了3条中的1条,那么就是说明其类是有意义(non- trivial)的:
- 显式(explict)定义了这四种函数
- 类里有非静态非POD的数据成员
- 有基类
何为POD?简单解释就是指C风格的struct结构体定义的数据结构或者C++的内建类型,POD类型必然有trivial ctor/dtor/copy/assignment四种函数。
那么区分构造函数是否有意义又有什么意义呢?
如果这个类都是trivial ctor/dtor/copy/assignment函数,我们对这个类进行构造、析构、拷贝和赋值时可以采用最有效率的方法,是不调用无所事事正真的那些ctor/dtor等,而是直接采用内存操作如malloc()、memcpy()等提高性能,获取最高效率。这对于大规模而操作频繁的容器,有着显著的效率提升。
而这个__type_traits机制就能针对不同的类特性,在编译时期完成函数的调用决定。这对于template很有帮助,例如,当我们准备对一个类型特性未知的数组进行copy操作时,如果我们能事先知道其元素的类型特性是否有一个trivial copy ctor,便能够帮我们我们决定是否可使用快速的memcpy()或memmove()。
与使用iterator_traits相似,我们可以这样运用__type_traits<T>:
1 __type_traits<T>::has_trivial_default_constructor 2 __type_traits<T>::has_trivial_copy_constructor 3 __type_traits<T>::has_trivial_assignment_operator 4 __type_traits<T>::has_trivial_destructor 5 __type_traits<T>::is_POD_type
我们希望通过调用这些式子来告诉我们T类型是否是有意义的(以便我们决定采取什么策略),但其结果不应该只是个bool值,应该是个有着真/假性质的“对象”,因为我们希望利用其相应结果来进行参数推导(等会给出代码例子),而编译器只有面对class object形式的参数,才会做参数推导。为此,上述式子应该传回这样的东西:
1 struct __true_type {}; 2 struct __false_type {};
这两个空白的结构体没有任何东西,不会带来额外的负担,却又能标示真假,满足我们所需。
为此,我们应该为上述的五个式子定义一些typedef,准确告知T是否有意义:
1 template <class type> 2 struct __type_traits { 3 typedef __true_type this_dummy_member_must_be_first; 4 /*不要移除这个成员,它通知「有能力自动将 __type_traits特化」的编译器说,我们现在所看到的这个 __type_traits template 是特殊的。这是为了确保万一编译器也使用一个名为 __type_traits而其实与此处定义并无任何关联的template时,所有事情仍将顺利运作*/ 5 6 /* 以下条件应该被遵守,因为编译器有可能自动为各类型产生专属的 __type_traits特化版本: 7 - 你可以重新排列以下的成员次序 8 - 你可以移除以下任何成员 9 - 绝对不可以将以下成员重新命名而却没有改变编译器中的对应名称 10 - 新加入的成员会被视为一般成员,除非你在编译器中加上适当支援 11 */ 12 typedef __false_type has_trivial_default_constructor; 13 typedef __false_type has_trivial_copy_constructor; 14 typedef __false_type has_trivial_assignment_operator; 15 typedef __false_type has_trivial_destructor; 16 typedef __false_type is_POD_type; 17 // 所谓 POD 意指 Plain Old Data structure. 18 };
this_dummy_member_must_be_first:因为某些编译器会提供__type_traits机制,自动会为所有类型提供适当的特化版本,所以为了将编译器内部的__type_traits和STL 自带的__type_traits区分开来,提供该特殊的定义式。
为什么SGI把所有的内嵌类型都定义为__false_type呢?这是最保守的做法,它默认认为所有的自定义类型都是具有有意义的构造函数,至于内部基本类型,SGI STL会为其提供特化版本。总的来说,上述的__type_traits可以接受任何类型的参数,但五个typedef会经由以下的管道获得实值:
- 自定义类型,内含对所有类型都必定有效的保守值。上述各个has_trivial_xxx型别都被定义为__false_type,就是对所有类型都必定有效的保守值。
- 经过声明的特化版本,例如<type_traits.h>内对所有C++基本类型提供了对应的特化声明。稍后展示。
- 某些编译器会自动为所有类型提供适当的特化版本。
以下便是<type_traits.h>对所有C++基本类型所定义的__type_traits特化版本。这些定义对于内建有__type_traits支持能力的编译器并无伤害,对于无该支持能力的编译器而言,实属必要。
1 /* 以下针对 C++ 基本型別 char, signed char, unsigned char, short, unsigned short, 2 int, unsigned int, long, unsigned long, float, double, long double 提供特化版本。 3 注意,每一个成员的值都是 __true_type,表示这些型別都可采用最快速方式(例如 memcpy) 4 來进行拷贝动作或赋值动作。*/ 5 6 //注意,SGI STL<stl_config.h>将以下出现的 _STL_TEMPLATE_NULL 7 //定义为template<>,是所谓的class template explicit specialization 8 9 __STL_TEMPLATE_NULL struct __type_traits<char> { 10 typedef __true_type has_trivial_default_constructor; 11 typedef __true_type has_trivial_copy_constructor; 12 typedef __true_type has_trivial_assignment_operator; 13 typedef __true_type has_trivial_destructor; 14 typedef __true_type is_POD_type; 15 }; 16 17 __STL_TEMPLATE_NULL struct __type_traits<signed char> { 18 typedef __true_type has_trivial_default_constructor; 19 typedef __true_type has_trivial_copy_constructor; 20 typedef __true_type has_trivial_assignment_operator; 21 typedef __true_type has_trivial_destructor; 22 typedef __true_type is_POD_type; 23 }; 24 25 __STL_TEMPLATE_NULL struct __type_traits<unsigned char> { 26 typedef __true_type has_trivial_default_constructor; 27 typedef __true_type has_trivial_copy_constructor; 28 typedef __true_type has_trivial_assignment_operator; 29 typedef __true_type has_trivial_destructor; 30 typedef __true_type is_POD_type; 31 }; 32 33 __STL_TEMPLATE_NULL struct __type_traits<short> { 34 typedef __true_type has_trivial_default_constructor; 35 typedef __true_type has_trivial_copy_constructor; 36 typedef __true_type has_trivial_assignment_operator; 37 typedef __true_type has_trivial_destructor; 38 typedef __true_type is_POD_type; 39 }; 40 41 __STL_TEMPLATE_NULL struct __type_traits<unsigned short> { 42 typedef __true_type has_trivial_default_constructor; 43 typedef __true_type has_trivial_copy_constructor; 44 typedef __true_type has_trivial_assignment_operator; 45 typedef __true_type has_trivial_destructor; 46 typedef __true_type is_POD_type; 47 }; 48 49 __STL_TEMPLATE_NULL struct __type_traits<int> { 50 typedef __true_type has_trivial_default_constructor; 51 typedef __true_type has_trivial_copy_constructor; 52 typedef __true_type has_trivial_assignment_operator; 53 typedef __true_type has_trivial_destructor; 54 typedef __true_type is_POD_type; 55 }; 56 57 __STL_TEMPLATE_NULL struct __type_traits<unsigned int> { 58 typedef __true_type has_trivial_default_constructor; 59 typedef __true_type has_trivial_copy_constructor; 60 typedef __true_type has_trivial_assignment_operator; 61 typedef __true_type has_trivial_destructor; 62 typedef __true_type is_POD_type; 63 }; 64 65 __STL_TEMPLATE_NULL struct __type_traits<long> { 66 typedef __true_type has_trivial_default_constructor; 67 typedef __true_type has_trivial_copy_constructor; 68 typedef __true_type has_trivial_assignment_operator; 69 typedef __true_type has_trivial_destructor; 70 typedef __true_type is_POD_type; 71 }; 72 73 __STL_TEMPLATE_NULL struct __type_traits<unsigned long> { 74 typedef __true_type has_trivial_default_constructor; 75 typedef __true_type has_trivial_copy_constructor; 76 typedef __true_type has_trivial_assignment_operator; 77 typedef __true_type has_trivial_destructor; 78 typedef __true_type is_POD_type; 79 }; 80 81 __STL_TEMPLATE_NULL struct __type_traits<float> { 82 typedef __true_type has_trivial_default_constructor; 83 typedef __true_type has_trivial_copy_constructor; 84 typedef __true_type has_trivial_assignment_operator; 85 typedef __true_type has_trivial_destructor; 86 typedef __true_type is_POD_type; 87 }; 88 89 __STL_TEMPLATE_NULL struct __type_traits<double> { 90 typedef __true_type has_trivial_default_constructor; 91 typedef __true_type has_trivial_copy_constructor; 92 typedef __true_type has_trivial_assignment_operator; 93 typedef __true_type has_trivial_destructor; 94 typedef __true_type is_POD_type; 95 }; 96 97 __STL_TEMPLATE_NULL struct __type_traits<long double> { 98 typedef __true_type has_trivial_default_constructor; 99 typedef __true_type has_trivial_copy_constructor; 100 typedef __true_type has_trivial_assignment_operator; 101 typedef __true_type has_trivial_destructor; 102 typedef __true_type is_POD_type; 103 }; 104 105 #ifdef __STL_CLASS_PARTIAL_SPECIALIZATION 106 107 template <class T> 108 struct __type_traits<T*> { 109 typedef __true_type has_trivial_default_constructor; 110 typedef __true_type has_trivial_copy_constructor; 111 typedef __true_type has_trivial_assignment_operator; 112 typedef __true_type has_trivial_destructor; 113 typedef __true_type is_POD_type; 114 }; 115 116 #else /* __STL_CLASS_PARTIAL_SPECIALIZATION */ 117 118 struct __type_traits<char*> { 119 typedef __true_type has_trivial_default_constructor; 120 typedef __true_type has_trivial_copy_constructor; 121 typedef __true_type has_trivial_assignment_operator; 122 typedef __true_type has_trivial_destructor; 123 typedef __true_type is_POD_type; 124 }; 125 126 struct __type_traits<signed char*> { 127 typedef __true_type has_trivial_default_constructor; 128 typedef __true_type has_trivial_copy_constructor; 129 typedef __true_type has_trivial_assignment_operator; 130 typedef __true_type has_trivial_destructor; 131 typedef __true_type is_POD_type; 132 }; 133 134 struct __type_traits<unsigned char*> { 135 typedef __true_type has_trivial_default_constructor; 136 typedef __true_type has_trivial_copy_constructor; 137 typedef __true_type has_trivial_assignment_operator; 138 typedef __true_type has_trivial_destructor; 139 typedef __true_type is_POD_type; 140 };
对于__type_traits的应用,我们可以举一个之前学习Allocator时就遇到的函数destroy():
1 // 以下是 destroy() 第二版本,接受兩個迭代器。此函式是設法找出元素的數值型別, 2 // 進而利用 __type_traits<> 求取最適當措施。 3 template <class ForwardIterator> 4 inline void destroy(ForwardIterator first, ForwardIterator last) { 5 __destroy(first, last, value_type(first)); 6 } 7 8 // 判斷元素的數值型別(value type)是否有 trivial destructor 9 template <class ForwardIterator, class T> 10 inline void __destroy(ForwardIterator first, ForwardIterator last, T*) { 11 typedef typename __type_traits<T>::has_trivial_destructor trivial_destructor; 12 __destroy_aux(first, last, trivial_destructor()); 13 } 14 15 // 如果元素的數值型別(value type)有 non-trivial destructor… 16 template <class ForwardIterator> 17 inline void 18 __destroy_aux(ForwardIterator first, ForwardIterator last, __false_type) { 19 for ( ; first < last; ++first) 20 destroy(&*first); 21 } 22 23 // 如果元素的數值型別(value type)有 trivial destructor… 24 template <class ForwardIterator> 25 inline void __destroy_aux(ForwardIterator, ForwardIterator, __true_type) {}
可以看到,在__destroy函数里,利用了__type_traits<T>机制判断T类型是否具有无意义的析构函数,然后再根据判断结果进行参数推导,调用相应的函数,如果是无意义的析构函数就不做很任何事情,提升性能;如果是有意义的析构函数,就为该区间里的所有对象逐一调用析构函数。
正如上所言,于一切自定义类型,__type_traits<T>机制默认认为其具有有意义的构造函数(除非是使用内部提供__type_traits机制的编译器就能自动识别该类型是否有意义,但大部分编译器缺乏这种特异功能),这样的结果过于保守,那么如何让__type_traits<T>机制能够为我们的自定义类型提取到真正的特性呢?答案就是自行为自己设计的类型提供__type_traits特化版本,明白地告诉编译器事实。举例,假设我自行定义了一个shape class,是一个没有默认构造函数的类型(即存在trivial defalt ctor),那么它的__type_traits特化版本应该是:
1 template<> struct __type_traits <Shade> { 2 typedef __true_type has_trivial_default_constructor; 3 typedef __false_type has_trivial_copy_constructor; 4 typedef __false_type has_trivial_assignment_operator; 5 typedef __false_type has_trivial_destructor; 6 typedef __false_type is_POD_type; 7 }
究竟一个类什么时候该有自己的non-trivial defalt ctor,non-trivial copy ctor,non-trivial assignment ctor,non-trivial dtor?一个简单的判断准则就是:如果类内包含指针成员,并且对它进行内存动态配置,那么这个类就需要实现出自己的non-trivial-xxx。