为了使迭代器不暴露容器的成员及函数,一般将迭代器的开发工作交由容器的设计者,因此,STL每一种STL容器都提供专属的迭代器。
迭代器类型识别
C++不支持typeof(),即便动用RTTI性质中的typeid(),获得的也只是类型名称,不能拿来声明变量。
解决办法一:利用function template的参数推导机制
转调的妙用主要在参数的变化上
- __uninitialized_copy转调__uninitialized_copy_aux:新增一个参数判断是否含有 non-triaval construct
- func() 转调func_impl():新增一个迭代器的解引用参数,用来推导迭代器所指对象的类型。
这种方法的缺陷:
- 参数推导机制能得到迭代器所示元素类型value_type,但迭代器里面一共有5种内型,其他类型没办法通过解引用的方式推导。
- 如果value type要做为返回值,这种方法也不能做到
解决办法二:迭代器内嵌类型声明
这种方法在class里声明类型,但并不是所有迭代器都是class type,比如原生指针。
解决办法三:模板偏特化
也就是把指针、迭代器(可以看成智能指针)的特性用一个类提取,然后对这个提取类进行偏特化。
普通迭代器:
按道理,原生指针用这种方案也是正确的,但是,原始指针并没有 value_type啊,所以对原生指针进行偏特化,原生指针虽然没有value type,但提取特征时,能够读取到由它推导的value type。(这算是功能解耦的一大好处吧)
查漏补缺:当原生指针类型为const int* 时,其指向的元素类型应该判断为int,以方便我们做 non-triaval之类的判断,但现在,它判断为 const int了,对此,还需设计另一个特化版本:
事实上,一个类型T被特化时,其const T往往也需要特化。
总结:traits是一个特性提取器
五种迭代器类型
上面讲完了value type怎么推导的,接下来看看迭代器的其他四种类型怎么推导
difference type
用来表示两个迭代器之间的距离,因此可以用来表示一个容器的最大容量。
这个type有啥用呢:如果一个泛型算法提供计数功能,如STL的count(),其传回值就必须使用迭代器的difference type。
测试用例:
int main(int argc, char const *argv[]) {
vector<int> src;
src.push_back(1);
src.push_back(2);
src.push_back(3);
vector<int>::iterator i1 = src.begin();
vector<int>::iterator i2 = src.end();
vector<int>::difference_type diff = i2 - i1;
cout << "diff = " << diff << endl;
return 0;
}
结果diff=3
针对原生指针(如vector的迭代器就是指向value type的原生指针),以C++内建的ptrdiff_t做为原生指针的difference type 。
reference type 和 pointer type
没啥好讲的,注意对原生指针以及对应的const版本做偏特化就行
迭代器类型iterator_category
直线与箭头代表的不是C++的继承关系,但可以当作继承关系,后面有讲继承的妙用。
以advance()为例,对于vector 迭代器(RAI),向前跳跃n个单位的时间复杂度是O(1),而对于list 迭代器(BI),向前跳跃n个单位的时间复杂度是O(n),因此,advance应根据不同的迭代器类型,做不同的向前跳跃操作:
但是这样有两个缺点:
- 运行时期才决定执行的函数,影响执行效率,最好在编译期间就决定。
- 如果迭代器类型又多了一种,这个函数也得改。if else不易于扩展
解决办法:前面讲value type不是可以进行类型推导,可以灵活绑定类型吗,同样,利用traits的思想,可以解决 iterator_category 面临的两个问题。
一、首先,将这5中类别定义为5个类。不用数字1,2,3,4,5,因为不方便类别扩展,也不方便做类型推导。也因为这些类只做类型推导,所以不需要任何成员,继承时不会造成额外开销(通过继承可实现传递调用:子类 FI 可以不用申明advance(),用父类 II 的就行)。
二、给advance添加一个类型推导参数,通过转调形成重载。
三、调用,主要就是推导__advance的第三个参数:类型。
这份工作当然就交给traits机制:
advance的转调用其实是__advance(i,n,iterator_category(i))
iterator_category()源代码如下:
// 这里的参数其实就是__advance的第一个参数i
// 这里可以直接得到迭代器的iterator_category,转调的原因是因为要符合STL标准
template <class _Iter>
inline typename iterator_traits<_Iter>::iterator_category
iterator_category(const _Iter& __i) { return __iterator_category(__i); }
// 提取特定迭代器的iterator_category
template <class _Iter>
inline typename iterator_traits<_Iter>::iterator_category
__iterator_category(const _Iter&)
{
typedef typename iterator_traits<_Iter>::iterator_category _Category;
return _Category();
}
// 特定迭代器(以RAI为例),返回自己的类型,进而找到对应的重载函数
template <class _Tp>
inline random_access_iterator_tag iterator_category(const _Tp*)
{ return random_access_iterator_tag(); }
四、traits类增加iterator_category成员。 至此,traits需要提取的迭代器特性都集齐了。
template <class _Iterator>
struct iterator_traits {
typedef typename _Iterator::iterator_category iterator_category;
typedef typename _Iterator::value_type value_type;
typedef typename _Iterator::difference_type difference_type;
typedef typename _Iterator::pointer pointer;
typedef typename _Iterator::reference reference;
};
SGI STL的私房菜: __type_traits
<stl_iterator_base.h>中的iterator_traits负责提取迭代器特性,说的都是推导迭代器的相关类型,而traits的思想,被SGI用到了更广泛的地方。
<type_traits.h> 这个文件是SCI独有的,不在STL标准之内。
__type_traits负责提取类型特性:比如一个类是否含有non-trivial 函数,是否是POD类型。有趣的是,类似 iterator_category(有5种取值)的提取,__type_traits将其5个成员的两种取值(yes、no)也申明为类了,方便做类型推导。
struct __true_type {
};
struct __false_type {
};
template <class _Tp>
struct __type_traits {
typedef __true_type this_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;
};
默认采用最严格的方式,全部设置为false,并针对不同的类,做了偏特化
__STL_TEMPLATE_NULL struct __type_traits<char> {
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;
};