STL traits萃取
一.迭代器萃取机iterator_traits
为了知道迭代器所指对象的型别,迭代器型别等等其它型别,STL中使用了一种萃取机制,通过iterator_traits来获取它们。但前提是所有迭代器都遵循约定,自行以内嵌型别定义的方式定义出相应型别,所谓内嵌型别定义就是在迭代器内部通过typedef关键字将一些类型定义为约定好的类型名。我们通过一个简单的例子来进行说明。
template<class T>
struct MyIter{
typedef T value_type;
typedef T& reference;
typedef T* pointer;
private
T* _ptr;
MyIter(T* ptr) : _ptr(ptr){}
};
template<class I>
struct iterator_traits{
typedef typename I::value_type value_type;
typedef typename I::reference reference;
typedef typename I::pointer pointer;
...
};
template<class T>
void fun(T a){
iterator_traits<T>::value_type aCopy = *a;
...
return aCopy ;
}
为什么不直接获取迭代器内部的内嵌型别呢?而是要通过iterator_traits作为中间件。好处在于iterator_traits拥有特化版本,可以处理原始指针。由于原始指针不是一种class类型,因此无法通过内嵌型别获取其指向的对象类型等信息。而iterator_traits的偏特化版本很好的解决了这些问题,其定义方式如下:
template<class T>
struct iterator_traits<T*>{
typedef T value_type;
typedef T& reference;
typedef T* pointer;
...
};
二. 迭代器的几种各内嵌型别
2.1 value_type
迭代器所指对象的型别,其用法在第一部分中已做了叙述。
2.2 difference_type
用于表示两个迭代器之间的距离,c++中使用ptrdiff_t(#define int ptrdiff_t)作为原始指针的difference_type。迭代器 中的该类型也用于表示容器大小,如STL中的count()函数:
template<class I,class T>
typename iterator_traits<I>::difference_type
count(I first, I last, const T& value) {
typename iterator_traits<I>::difference_type n = 0;
for(; first != last; ++first){
if(*first == value)
++n;
}
return n;
}
2.3 reference 与 const_reference
在c++中如果要传回一个左值,那么一定是通过引用的形式,如过传回的不是引用而是一个对象,则会产生临时对象, 采用的是右值的方式进行传回的。因此要传回左值,返回类型应该为T&或const T&,在迭代器中定义为: typedef T& reference; typedef const T& const_reference;
2.4 pointer
pointer是迭代器所指类型的原始指针类型,即其可以指向迭代器所指之物。定义如下:typedef T* pointer;
2.5 iterator_category
即说明迭代器类型,迭代器有五种类型:1)Input Iterator:只读迭代器,不允许通过这种迭代器改变迭代器所指对象。 2)Output Iterator:只写迭代器。3)Forward Iterator:在此种迭代器所形成的区间上可进行读写操作。以上三种迭代器只支持 operator++操作。4) Bidirectional Iterator 双向迭代器,即支持++,也支持--运算符。5)Random Access Iterator:随机迭 代器,支持p++,p--,p+n,p-n,p[n],p1-p2,p1 < p2。
为了在编译期就能根据迭代器类型决定采用哪个函数,而不是等到运行期再去判断。这就要求迭代器型别必须是一个 class type而非数值,字符之类的东西。迭代器型别的五个类型定义如下:
struct input_iterator_tag{};
struct output_iterator_tag{};
struct forward_iterator_tag : public input_iterator_tag{};
struct bidirectional_iterator_tag : public forward_iterator_tag{};
struct random_access_iterator_tag : public bidirectional_iterator_tag {};
这些类别采用继承机制的原因要从STL算法的命名规则说起,我们知道,迭代器是容器与算法之间的桥梁,而是算法的 命名规则是:以算法所能接受的最低阶迭代器类型,来为其迭代器型别参数命名。比如:当一个算法可以接受 input_iterator_tag为参数时,比可以接受后三种迭代器作为参数,因为input_iterator_tag支持的操作后三种迭代器必然支 持。可以将子类种迭代器看作是父类迭代器的加强。比如advance函数的命名:
template <class InputIterator, class Distance>
inline void advance(InputIterator& i, Distance n){ // 以InputIterator进行命名
// 在算法内部萃取出迭代器类型,来决定调用哪个重载函数
__advance(i, n, iterator_traits<InputIterator>::iterator_category());
}
为了能与STL紧密融合,每一种新设计的新设计的迭代器都应该包括上述五种相应型别,为了防止漏写,可以继承STL中的 iterators class,其定义如下:
template<class Category,
class T,
class Distance = ptrdiff_t,
class Pointer = T*,
class Reference = T&>
struct iterator{
typedef Category iterator_category;
typedef T value_type;
typedef Distance difference_type;
typedef Pointer pointer;
typedef Reference reference
}
三.traits编程技巧在其它地方的使用
除了iterator_traits,在STL内部还使用了__type_traits来萃取型别的特性,这些特性包括:是否有非默认的构造函数?是否有非默认的·拷贝构造函数?是否有非默认的赋值运算符?是否有非默认的析构函数?是否为POD类型?就像在博文《STL 空间配置器(三)》中__uninitialized_copy函数中使用的那样,可以避免对那些不用调用构造函数的型别直接采取memmove操作,从而提高效率。
__type_traits的设计如下:
struct __true_type{};
struct __false_type{};
template<class type>
struct __type_traits{
// 防止编译器定义了同名的模板类型
typedef __true_type has_dummy_member_must_be_first;
typedef __false_type has_trivial_default_constructor; // 只有默认的构造函数
typedef __false_type has_trivial_copy_constructor;
typedef __false_type has_trivial_assignment_operator;
typedef __false_type has_trivial_destructor;
typedef __false_type is_POD_type;
};
STL先保守的假设都包括非默认构造函数,接着再针对每一个标量设计一个特化版本,举个例子:
template<class type>
struct __type_traits{
typedef __true_type has_trivial_default_constructor; // 只有默认的构造函数
typedef __true_type has_trivial_copy_constructor;
typedef __true_type has_trivial_assignment_operator;
typedef __true_type has_trivial_destructor;
typedef __true_type is_POD_type;
};
类似的还有int, float等等。
看到这里如果我们细细思考会发现这样一个问题,如果是我们自己定义的类型,那么能否萃取出来呢?以下是在vs2015上 进行的一个测试
struct TestBase1 {
int a;
};
struct TestStruct1 {
TestBase1 t;
int64_t num[5];
};
struct TestBase2 {
TestBase2(){}
int a;
};
struct TestStruct2 {
TestBase1 t;
int64_t num[5];
};
int main()
{
cout << is_trivial<TestStruct1>::value << std::endl; // 输出: 1(即true)
cout << is_trivial<TestStruct2>::value << std::endl; // 输出: 0(即false)
return 0;
}
我们发现是可以萃取出来的,可这是为什么呢?STL中并没有对自定义结构的特化啊。其实这是由于编译器在背后做了手脚,一些较弱的编译器便不能萃取出来,即使是POD类型,萃取出的每个特性仍是__false_type。