STL源码剖析iterators与traits

iterator概述

迭代器是一种抽象的设计概念。《Design Patterns》中对iterator的定义如下:提供了一种方法,使之能够依序巡防某个聚合物(容

器)所含的各个元素,而又无需暴露聚合物的内部表述方法。

迭代器最重要的编程工作是对operator*和operator->进行重载

迭代器的相应型别(associated types)

在算法中运用迭代器的时候,可能会使用到迭代器的相应型别。迭代器所指对象的型别就是其一。

C++只支持sizeof(),不支持typeof()。即便动用RTTI性质中的typeid(),获得的也只是型别名称,不能拿来做变量声明之用

解决办法:

利用function template的参数推导机制

template <class I,class T>
void func_impl(I iter,T t)
{
    T tmp;//T 就是迭代器所指之物
    ...
    //这里做func()原本要做的原本工作
}
template<class I>
inline
void func(I iter)
{
    func_impl(iter,*iter);//func的全部工作移往func_impl
}
int main()
{
    int i;
    func(&i);
}

我们使用功能func()为对外接口,但是实际工作全部在func_impl中来实现。

func_impl一旦被调用,编译器会自动进行template参数推导。于是导出型别T。

最常用的相应型别为五种,并不是任何情况下任何一种都可以利用上述的template参数推导机制来获取。我们需要更加全面的解法

为了使容器和STL水乳交融,一定要为容器的迭代器定义以下五种型别。

template <class I>
struct iterator_traits{
    typedef typename I:iterator_category iterator_catefory;
    typedef typename I::value_type value_type;  
    typedef typename I:difference_type difference_type;  
    typedef typename I::pointer pointer;  
    typedef typename I::reference reference;  
}

上述模板参数型别推导虽然可以用于value_type,却非全面可用:当value_type必须作为函数的传回值,就束手无策了。

因为template参数推导机制只能获取参数,无法推导函数的返回值类型。

于是STL中采用内嵌型别的方法:

template<typename T>  
class Iterator  
{  
public:  
    typedef T value_type;//内嵌类型声明  
    Iterator(T *p = 0) : m_ptr(p) {}  
    T& operator*() const { return *m_ptr;}  
    //...  
  
private:  
    T *m_ptr;  
};  
  
template<typename Iterator>  
typename Iterator::value_type  //以迭代器所指对象的类型作为返回类型,长度有点吓人!!!  
func(Iterator iter)  
{  
    return *iter;  
}  
  
int main(int argc, const char *argv[])  
{  
    Iterator<int> iter(new int(10));  
    cout<<func(iter)<<endl;  //输出:10  
}  

函数func()的返回类型前面必须加上关键词typename。因为T是一个template参数,编译器在编译实例化func之前,对T一无所知,就是说,编译器并不知道Iterator<T>::value_type是一个类型,或者是一个静态成员函数,还是一个静态数据成员,关键词typename的作用在于告诉编译器这是一个类型,这样才能顺利通过编译。

但是这里又有一个隐晦的陷阱:并不是所有迭代器都是类类型class type。原生指针是一种迭代器,就不是类类型。


偏特化Partial Specialization

template<typename U,typaname V,typename T>
class c{...};

partial specialization字面的意思容易误导我们以为,偏特化是对template的参数U,V,T指定某个参数值。

正确的理解是,针对template参数更进一步的条件限制所设计出来的一个特化版本。

当泛化版本为

template<typename T>
class C{...};

那么特化版本就可以设置为

template<typename T>
class C<T*> {...};

于是,我们利用偏特化版本来解决迭代器的template参数为指针的情况。

于是STL中设置一个模板类,也就是萃取类,来实现这一功能

template<typename I>  //泛化版本
struct iterator_traits  
{  
    typedef typename I::value_type value_type;  
}; 

template<typename T>  //偏特化版本  原生指针
struct iterator_traits<T*>  
{  
    typedef T value_type;  
}; 

template<typename T>  //偏特化版本  原生const指针
struct iterator_traits<const T*>  
{  
    typedef T value_type;  
}; 

因此,现在迭代器萃取的任务得到解决。无论是类类型迭代器,还是原生指针int*或者const int* 都可以得到正确的value_type。

这里需要注意:

容器的迭代器是容器开发者进行设定的。而特性萃取剂iterator_traits是STL指定的。

因此,你的迭代器进行设计的时候一定要定义前述的五种相应型别。

相应型别一:value_type

        value_type就是指迭代器所指对象的类型,例如,原生指针也是一种迭代器,对于原生指针int*,int即为指针所指对象的类型,也就是所谓的value_type。

相应型别二:difference type

difference_type用来表示两个迭代器之间的距离

对于原生指针,STL以C++内建的ptrdiff_t作为原生指针的difference_type。

相应型别三:reference type

reference_type是指迭代器所指对象的类型的引用,reference_type一般用在迭代器的*运算符重载上,如果value_type是T,那么对应的reference_type就是T&;如果value_type是const T,那么对应的reference_type就是const T&。

相应型别四:pointer type

pointer就是指迭代器所指的对象,也就是相应的指针,对于指针来说,最常用的功能就是operator*和operator->两个运算符。因此,迭代器需要对这两个运算符进行相应的重载工作:

T& operator*() const { return *ptr; } // T& is reference type  
T* operator->() const { return ptr; } // T* is pointer type  

相应型别五:iterator_category

iterator_category的作用是标识迭代器的移动特性和可以对迭代器执行的操作,从iterator_category上,可将迭代器分为Input Iterator、Output Iterator、Forward Iterator、Bidirectional Iterator、Random Access Iterator五类,具体为什么要这样分类,简单来说,就是为了尽可能地提高效率,这也是STL的宗旨之一。

Input Iterator:这种迭代器所指对象不允许外界改变。

Output Iterator:只能写,不能读。

Forward Iterator:可读,可写

Bidirectional Iterator:可以双向移动

RandomAccess Iterator:前四种只提供了一部分指针运算能力。前三种支持operator++,第四种还支持operator--,第五种涵盖所有指针计算能力,包括p+n,p-n,p[n],p1-p2,p1+p2。

迭代器的分类和从属关系

迭代器型别的巧用和重载

在STL中,效率是一直需要考虑的,当需要一个Input Iterator的时候,传入一个RandomAccess Iterator肯定不会接受,虽然可用但不是最佳。

一个算法可能接受多种迭代器,为了效率需要确定那种迭代器后再执行对应的函数。

为了避免能够在编译的时候就选择正确的版本,重载函数机制可以达到这个目标。所以需要通过迭代器的类型来实现对算法函数的重载。那么需要该型别是class type而不是数值号码类的东西,因为编译器需要依赖它来进行重载决议(overloaded resolution)

//五种迭代器型别
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 {};

这些classes只作为标记用,所以不需要任何成员。

以advance算法为例:

需要注意这里的forward iterator是进行了单纯的传递调用,后面会讲如何消除传递调用。

//InputIterator类型定义
template<class InputIterator, class Distance>
inline void __advance(InputIterator& i, Distance n, input_iterator_tag) {
    while (n--) ++i;
}

//ForwardIterator类型定义
//单纯的传递调用(trivial forwarding function)
template<class ForwardIterator, class Distance>
inline void __advance(ForwardIterator& i, Distance n, forward_iterator_tag) {
    _advance(i,n,input_iterator_tag());
}


//BidirectionalIterator类型定义
template<class BidirectionalIterator, class Distance>
inline void __advance(BidirectionalIterator& i, Distance n, 
                      bidirectional_iterator_tag) {
    if (n >= 0)
        while (n--) ++i;
    else
        while (n++) --i;    
}

//RandomAccessIterator类型定义
template<class RandomAccessIterator, class Distance>
inline void __advance(RandomAccessIterator& i, Distance n,
                      random_access_iterator_tag) {
    i += n;
} 

这里还需要一个对外开放的上层控制接口,调用上述的四个函数。 

template<class InputIterator, class Distance>
inline void advance(InputIterator& i, Distance n) {
    __advance(i, n,iterator_traits<InputIterator>::iteraot_category();)
}

任何一个迭代器,其类型永远应该在该迭代器所隶属之各种类型中,最强化的那个。int*属于四个迭代器类型,但是它应该归属于random_access_iterator_tag。

注意到advance()的template参数名称好像不理想。

template<class InputIterator, class Distance>
inline void advance(InputIterator& i, Distance n)

advance既然可以接受各种类型的迭代器,就不应该将其型别参数命名为InputIterator。这其实是STL算法的命名规则,以算法所能够接受最低阶迭代器类型来为其迭代器型别参数命名

由于之前的五种迭代器分类标签以继承形式进行了定义,所以在上述函数advance中,可以将forward iterator的函数去掉。当传入的是一个forward_iterator_tag的时候,由于继承关系,自动匹配到其父类,input_iterator_tag中。从而调用input iterator的函数。

std::iterator的保证

为了符合规范,任何迭代器都应该提供五个内嵌型别,以利于traits萃取,否则就是自别与整个STL架构,可能无法与其它STL组件顺

利搭配。为了方便设计新的iterator,STL提供了一个iterator 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;
}

iterator class 不包含任何成员,纯粹只是型别定义,所以继承它不会导致任何额外负担。后三个参数都有默认值,所以新的迭代器只

需要提供前两个参数就可以。

SGI STL的私房菜:__type_traits

STL只对迭代器加以规范,制定出了iterator_traits这样的东西。

SGI把这种技法进一步扩大到迭代器以外的时间,于是有了所谓的__type_traits。

双底线前缀标定这是STI STL内部所用的东西,不在STL标准规范之内

iterator_traits负责萃取迭代器的特性,__type_traits负责萃取型别的特性。这里我们关注的型别特性主要是指:

  • 是否具备non-trivial default ctor?
  • 是否具备non-trivial copy ctor?
  • 是否具备non-trivial assignment operator?
  • 是否具备non-trivial dtor?
  • 是否为POD(plain old data)型别?

其中non-trivial意指非默认的相应函数,编译器会为每个类构造以上四种默认的函数,如果有非默认的函数,就会用编译器执行该函数,如果使用默认的函数,我们可以使用memcpy(),memmove(),malloc()等函数来加快速度,提高效率.

且__iterator_traits允许针对不同的型别属性在编译期间决定执行哪个重载函数而不是在运行时才处理, 这大大提升了运行效率. 这就需要STL提前做好选择的准备. 是否为POD, non-trivial型别用__true_type和__false_type 来区分.

对于两个参数我们不能将参数设为bool值, 因为需要在编译期就决定该使用哪个函数, 所以需要利用函数模板的参数推导机制, 将__true_type__false_type表现为一个空类, 就不会带来额外的负担, 又能表示真假, 还能在编译时类型推导就确定执行相应的函数.

struct __true_type {};
struct __false_type {};

然后是SGI对于__type_traits的泛化版本,首先把所有内嵌型别都做最保守的值,然后针对每一个标量型别设计适当的__type_traits的

特化版本。

template <class type>
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;
};

以char*为例的特化版本

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;
};

        上述五个typedefs通过以下途径获取实值:

  • 一般具现体,内含对所有型别都必定有效的保守值。比如都设置为__false_type。
  • 经过声明的特化版本,例如:<type_traits.h>内对所有C++标量型别提供了对应的特化声明。
  • 某些编译器会自动为所有型别提供适当的特话版本。

SGI对traits进行扩展,使得所有类型都满足traits编程规范, 这样SGI STL算法可以通过__type_traits获取类型信息在编译期间就能决定

出使用哪一个重载函数, 解决了template是在运行时决定重载选择的问题. 并且通过true和false来确定POD和travial destructor, 让程

序能选择更加符合其类型的处理函数, 大大提高了对基本类型的快速处理能力并保证了效率最高.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章