Ch3 迭代器概念与traits编程技法

3.1 迭代器设计思维

STL的中心思想在于:将数据容器(containers)和算法(algorithms)分开,彼此独立设计,最后再以一帖胶着剂将它们撮合在一起,迭代器(iterators)则扮演胶着剂的角色。


3.2 迭代器是一种smart pointer

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

关于这一点,C++标准程序库有一个auto_ptr可供我们参考。

auto_ptr是一个用来包装原生指针(native pointer)的对象,memory leak问题也可借此解决。

每一种STL容器都提供有专属的迭代器,因为在完成一个针对某STL容器而设计的迭代器时,往往不可避免地暴露了该容器太多的实现细节。


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

利用function template的参数推导(argument deducation)机制,

以func()为对外接口,但把实际操作全部置于func_impl()之中,

由于func_impl()是一个function template,一旦被调用,编译器会自动进行template参数推导,

于是导出型别T,从而获取迭代器的相应型别。


3.4 Traits编程技法

最常用到的迭代器相应型别有:value type,difference type,pointer,reference,iterator category。

3.4.1 value type

template <class I>
struct iterator_traits {
    typedef typename I::value_type value_type;
};

traits的意义是,如果I定义有自己的value type,则通过这个traits的作用,萃取出来的value type就是I::value_type。从而,func()可以改写为:

template <class I>
typename iteratro_traits<I>::value_type func(I ite){
    return *ite;
}

这样,我们只需更改class type,则不论是常规的class type,还是原生指针(如int*),还是指向常数对象的指针,都可以通过traits来取得器value type。

3.4.2 different type

difference type 用来表示两个迭代器之间的距离,即用来表示一个容器的最大容量。

如STL的count(),其传回值就必须使用迭代器的difference type:

template <class I, class T>
typename iterator_traits<I>::difference_type count(I first, I last, cosnt T& value){
    typename iterator_traits<I>::difference_type n=0;
    for( ; first!=last; ++first){
        if(*first == value)  ++n;
    return n;
}

traits内部的特化:

template <class I>
struct iterator_traits{
    ...
    typedef typename I::difference_type difference_type;
};

//针对原生指针而设计的“偏特化(partial specialization)”版
template <class T>
struct iterator_traits<T*> {
    ...
    typedef ptrdiff_t difference_type;
};

//针对原生的pointer-to-const而设计的“偏特化”版
template <class T>
struct iterator_traits<const T*> {
    ...
    typedef ptrdiff_t difference_type;
};

 

3.4.3 reference type

constant iterators:不允许改变“所指对象之内容”的迭代器;

mutable iterators:允许改变“所指对象之内容”的迭代器。


对于mutable iterator进行赋值时,应该获得一个左值,因为右值不允许赋值操作(assignment)。

在C++中,函数如果要传回左值,都是以by reference的方式进行,所以当p是一个mutable iterators时,如果其value type是T,那么*p的型别应该是T&,而不是T(const也同理)。

上述*p的型别T&,即reference type。

3.4.4 pointer type

// ListIter class:
Item& operator*() const { return *ptr; }
Item* operator->() cosnt { return ptr; }

Item& :ListIter的reference type;

Item* :ListIter的pointer type。


traits内部对reference type和pointer type这两个相应型别的特化:
template <class I>
struct iterator_traits {
    ...
    typedef typename I::pointer pointer;
    typedef typename I::reference reference;
};

//针对原生指针而设计的“偏特化”版
template <class T>
struct iterator_traits<T*> {
    ...
    typedef T* pointer;
    typedef T& reference;
};

//针对原生的pointer-to-const而设计的“偏特化”版
template <class T>
struct iterator_traits<const T*> {
    ...
    typedef const T* pointer;
    typedef const T& reference;
};

3.4.5 iterator_category

五类迭代器:

  • Input Iterator:只读,这种迭代器所指的对象,不允许外界改变;
  • Output Iterator:只写;
  • Forward Iterator:允许“写入型”算法(如replace())在此种迭代器所形成的区间上进行读写操作;
  • Bidrectional Iterator:可双向移动。某些算法需要逆向走访某个迭代器区间,可以使用此迭代器;
  • Random Access Iterator:涵盖前四种迭代器的算术能力。

其概念的关系如下:

Input It     →   Forward It  →   Bidrectional It  → Random Access It

Output It   →


那么对于一个算法,我们将写好五种迭代器所对应的版本,在编译时(之所以不在执行时才决定,是避免影响程序效率)决定应该使用哪一个版本。

要达到这个目标,可以如下设计:利用重载函数,并通过traits萃取出迭代器的种类(iterator categories),利用“迭代器类型”相应型别作为算法的最后一个参数。

//五个作为标记用的型别(tag types)
struct input_iterator_tag { };
struct ouput_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 { };

这些calsses只作为标记用,故不需要任何成员。

现以advance()为例,利用上述classes重新设计:

/*先设计__advance() */
//由于只在内部使用,所以函数名称前面加了__
template <class InputIterator, class Distance>
inline void __advance(InputIterator& i, Distance n, input_iterator_tag) {
    while(n--) ++i;
}

template <class ForwardIterator, class Distance>
inline void __advance(ForwardIterator& i, Distance n, forward_iterator_tag) {
    //进行单纯的传递调用
    __advance(i, n, input_iterator_tag);
}

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

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

/*增加一个对外开放的上层控制接口,调用上述各个重载的__advance() */
//这里template的第一个参数名称取为InputIterator是因为
//遵守STL算法命名的规则:
//以算法所能接受之最低阶迭代器类型,来为其迭代器型别参数命名
template <class InputIterator, class Distance>
inline void advance(InputIterator& i, Distance n) {
    __advance(i, n, iterator_traits<InputIterator>::iterator_category());
}

/*为traits增加相应型别iterator category */
template <class I>
struct iterator_traits {
    ...
    typedef typename I::iterator_category iterator_category;
};

//针对原生指针而设计的“偏特化”版
template <class T>
struct iterator_traits<T*> {
    ...
    //原生指针是一种Random Access Iterator
    typedef random_access_iterator_tag iterator_category;
};

//针对原生的pointer-to-const而设计的“偏特化”版
template <class T>
struct iterator_traits<const T*> {
    ...
    //原生的pointer-to-const也是一种Random Access Iterator
    typedef random_access_iterator_tag iterator_category;
};

//任何一个迭代器,其类型永远落在“该迭代器所隶属之各种类型中,最强化的那个”
//如int*,既是Random Access Iterator,又是Bidirectional Iterator,也是Forward Iterator,
//也是Input Iterator,那么int*的类型应该是random_access_iterator_tag

为了消除单纯的传递调,前面用来定义五种迭代器的classes,其中设置好的继承关系,就可以派上用场:

//以I, F, B为例

//classes
struct I { };
struct F : public I { }; //继承I
struct B : public F { }; //继承F

//函数调用
template <class T>
func(T& p, I) {
    cout<<"I version\n";
}

template <class T>
func(T& p, B) {
    cout<<"B version\n";
}

int main(){
    int* p;
    func(p,I());  //参数与参数完全吻合,输出: “I version”
    func(p,F());  //参数与参数不完全吻合,因继承关系而自动传递调用,
           //输出: “I version”
    func(p,B());  //参数与参数完全吻合,输出: “B version”
}
posted @ 2017-01-18 13:56  &ATM  阅读(164)  评论(0编辑  收藏  举报
……