STL中的迭代器与traits技术
一,STL对其中的各个组件都有一定的规范要求,即如果自己实现其中的某个组件,那么这个组件必须符合STL规范才能与其他的STL组件兼容。
上篇在提到空间配置器的时候也提到过必须实现rebind,allocate,deallocate,construct,destroy等方法,在STL中,迭代器的类型需要由容器获得,即假如我们需要一个vector的迭代器,需要vector<T>::iterator it;这样实现,这也说明了在vector中必须将其对应的迭代器的类型作为模板类的成员。
在STL中,规定迭代器必须实现以下成员,即value_type, pointer, reference, difference_type, iterator_category,其中前四个均为类型成员,第五个用来表示迭代器的种类,是一个没有任何字段的类。
所以任何按照STL规范实现的迭代器都会有类似这样的语句:
{ typedef T value_type typedef T* pointer typedef T& reference typedef ptrdiff_t different_type typedef input_iterator_tag iterator_category ..... }
前四个非常好理解,因为在STL中得到迭代器it之后,也需要得到it所指的变量的类型,以及对应的指针类型,等等。第五个是用来区分迭代器的行为的,比如在计算两个迭代器之间的距离时,若他们是随机存储迭代器,则直接相减就能得到结果,否则需要一步一步试探,直到两个相等。
为了标记迭代器的类型,STL定义了五个没有任何成员的类型,
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_iterator_tag :public bidirectional_iterator_tag {}
这五个类型分别代表 :
只读迭代器, 只写迭代器, 读写迭代器, 双向读写迭代器, 随机存储读写迭代器
配合继承的机制,让STL的算法设计者们可以利用重载与类型转换的机制,避免函数调用带来的额外开销,比如要实现一个用于读写迭代器的操作
template<typename T>
void frw(T it, forward_iterator_tag) { }
由于双向读写迭代器支持读写迭代器的所有操作,为了上层接口统一,还需要一个这样的重载
template<T>
void frw(T it, bidirectional_iterator_tag) {
frw(it, forward_iterator_tag);
}
但是如果将bidirectional_iterator_tag 继承自 forward_iterator_tag的话,利用子类可以向父类转换的机制,可以将第二个重载中的函数的运行时开销转换为隐式类型转换的开销和编译时函数匹配的开销,由于这些tag并没有任何成员变量,所以可以完全避免这额外的一次运行时函数调用,在编译时就完成正确的函数匹配。
同时还可以为bindirectonal_iterator_tag提供更高效的版本的frw函数。
由于STL中的迭代器都实现了iterator_category成员,所以在编写泛型算法的时候可以根据具体的迭代器类型使用不同的实现方式,达到即不失一般性的同时获得很高的运行效率的目的
二,使用traits萃取技术,提取迭代器的属性
为什么要使用traits技术呢,按照上面的说法,一个接受一对迭代器的泛型算法,这样调用基本上就已经把问题解决了:
template<typename T> void f(T it1, T it2) { _f(it1, it2, typename T::iterator_category()); }
但是需求不仅仅是这么简单,我们都知道在STL中原生指针也是可以当成迭代器使用的,但是原生指针并没有叫做iterator_category的类型成员,这样原生指针似乎就不能与STL的其他组件相兼容了,在这里traits技术就派上用场了。
traits技术有时候也被翻译成萃取技术,意指利用traits技术可以获取某一个类型的特征,在STL的迭代器部分,SGI STL的大神们构造了这样一个萃取机:
//泛型,针对符合STL标准的迭代器 template <typename 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; }; //偏特化,针对原生指针 template<typename T> struct iterator_traits<T*> { typedef typename ramdon_access_iterator iterator_category; typedef typename T value_type; typedef typename ptrdiff_t difference_type; typedef typename T* pointer; typedef typename T& reference; };
同时还有为了保留原生指针的低层const属性的const T*偏特化,这里不再赘述。
有了iterator_traits,我们就可以这样:
template<typename T> void f(T it1, T it2) { _f(it1, it2, iterator_traits<T>::iterator_category()); }
如此一来,就完成了迭代器和原生指针的接口统一,同时还不浪费半点运行时开销,最大化的将工作挪到编译时完成。
值得一提的是,SGI STL的内存分配器也同样利用了traits技术,将有自己的析构函数、拷贝构造函数的自定义类型与内置类型区分开来,在构造内置类型是,使用memmove高效率拷贝,而不显示的使用定位new,并且直接跳过内置类型的析构函数。大致是这么实现的:
//标签,类似迭代器的xxx_tag struct __true_type {}; struct __false_type {}; //泛型采取保守策略 template <typename T> 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_destructor; typedef __false_type is_POD_type; };
这里POD只该类型不涉及指针操作,只需要使用值拷贝(即低层使用memmove即可)就可以完成构造等操作的类型。此外针对每种内置类型,STL都特例话了一个模板,这里不再赘述。
此外这里定义的typedef __true_type this_dummy_member_must_be_first应该是与那些可以自动为每个类型产生__type_traits特例的编译器的约定,注释中提到的能够自动特例化__type_traits的Silicon Craphics N32 和 N64编译器就是SGI公司制作的,所以这应该是公司内部的一些约定。
即,编译器可能先找到第一个成员为this_dummy_member_must_be_first的__type_traits作为基础,依照某个规则,为每个类型生成与这个有this_dummy_member_must_be_first的成员相关的__type_traits特例。
所以对于那些可能自动为每个类型生成__type_traits的编译器,需要这么这个成员来保证编译器的正常运作。具体规则应该需要去找那些奇怪的编译器的使用手册,So,此处无需再钻牛角尖。只要清楚即使有了这个奇怪的成员,即不会带来任何运行时开销,也不会影响任何SGI STL组件的正常运作即可
浙公网安备 33010602011771号