STL源码剖析--迭代器概念与traits技法

迭代器概念与traits技法

3.1 迭代器设计思维

STL的中心思想在于:将数据容器算法分开。而迭代器就是将数据容器与算法连接起来。

以find()函数为例:

template<class InputIterator,class T>
InputIterator find(InputIterator first,
                    InputIterator last,
                    const T& value){
                        while(first!=last && *first!=value)
                            ++first;
                        return first;
}
int main()
{
    vector<int> ivec{0,1,2,3,4,5,6};
    vector<int>::iterator it1=find(ivec.begin(),ivec.end(),4);
    if(it1 == ivec.end())
        cout<<"4 not found."<<endl;
    else
        cout<<"4 found."<<*it1<<endl;
}

//事实上,只要给予不同迭代器,find()便能够对不同容器进行查找操作。

3.2 迭代器是一种smart pointer

迭代器的功能与指针相类似,指针最常见的功能是deferencemember access,因此迭代器的工作就是对operator*operator->进行重载。

我们尝试为list设计一个迭代器:

template<class Item>
struct ListIter
{
    Item* ptr;
    ListItem(Item* p=0)
        :ptr(p){ }
    Item& operator*() const { return *ptr;}
    Item* operator->() const { return ptr;}

    ListIter& operator++()
    {
        ptr=ptr->next();return *this;
    }
    ListIter operator(int)
    {
        ListIter tmp=*this;++this;return tmp;
    }

    bool operator==(const ListIter& i) const
    {
        return ptr == i.ptr;
    }
    bool operator!=(const ListIter& i)
    {
        return ptr != i.ptr;
    }
};
//现在将list和find()用ListIter联合起来
int main()
{
    List<int> mylist; 

    for(auto i:{1,2,3,4,5}){
        mylist.insert_front(i);
        mylist.insert_end(i+2);
    }
    mylist.display();

    //ListItem为单向链表
    ListIter<ListItem<int>> begin(mylist.front());
    ListIter<ListItem<int>> end;
    ListIter<ListItem<int>> iter;

    iter=find(begin,end,3);
    if(iter == end)
        cout<<" not found."<<endl;
    else
        cout<<" found."<<iter->value()<<endl;
}

由于find()函数内以*iter!=value来检查元素值是否吻合,但是iter的类型为ListItem<int>,所以要写一个全局operator!=重载函数。

template<typename T>
bool operator!=(const ListItem<T>& item,T n){
    return item.value()!=n;
}

3.3迭代器相应型别

如果在运用迭代器的时候,我们需要使用迭代器所指对象的类型(相应型别之一),我们应该怎么做?
* 利用function template的参数推导机制。

template <class I,class T>
void func_impl(I iter,T t)
{
    T tmp;//T就是迭代器所指之物的类型
    ...
};

template <class I>
void func(I iter)
{
    func_impl(iter,*iter);
}

int main()
{
    int i;
    func(&i);
}

我们以func()作为对外接口,却把实际操作置于func_impl()之中。func_impl()是一个function template,一旦被调用,编译器会自动进行自动template参数推导。于是导出型别T,顺利解决问题。

但是,迭代器相应型别不只是“迭代器所指对象的类型”一种,根据经验,常见的相应型别有五种,然而并非任何情况下任何一种都可以利用上述的template参数推导机制来取得。

3.4 Traits编程技法——STL源代码门匙

迭代器所指对象的类型,称为该迭代器的value type。上述的技巧可以用于value type,但是如果value type用于函数的传回值,那么上述方法就不管用了,毕竟template参数推导机制只能推导参数,无法推断函数的返回值类型。那么该怎么做?

声明内嵌类型是个不错的主意:

template <class T>
struct MyIter{
    typedef T value_type;   //内嵌型别声明
    T* ptr;
    MyIter(T* p=0):ptr(p){ }
    T& operator*() const { return *ptr;}
    // ...
};

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

T为一个template参数,在它被编译器具现化之前,编译器不知道MyIter<T>::value_type为一个类型或者member function或者data membertypename是告诉编译器这是一个类型。

这只是对于class type的迭代器,但是对于原生指针就不行了,所以STL将它们进行了偏特化(template partial specialization)

假设有一个class template如下:

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

那么其偏特化为:

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

//这个特化版本仅适用于"T为原生指针"的情况
//"T 为原生指针便是”T为任何类型"的更进一步的条件限制

现在,我们可以针对“迭代器之template参数为指针”者,设计特化版的迭代器。

下面的class template来萃取迭代器的特性,value type为特性之一:

template <class I>
struct iterator_traits{     //traits为“特性”
    typedef typename I::value_type value_type;
};

对于原生指针,则设计一个偏特化版本如下:

template<typename T>
struct iterator_traits<T*>{
    typedef T value_type;
};

对于iterator_traits<const int*>::value_type,我们通过上面的特化版本获得的是const int而不是int,这不是我们想要的结果。
对此,我们也可以对其设计一个特化版本:

template<typename T>
struct iterator_traits<const T*>{
    typedef T value_type;       //萃取出来的就是 T 而不是 const T
};

traits就像是一台“特性萃取机”,榨取各个迭代器的特性。

最常用的迭代器相应类型有:

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

3.4.2 difference_type

difference_type用来表示两个迭代器之间的距离,因此可以用来表示容器的最大容量。

如果一个泛型算法提供计数功能,则传回值必须使用difference_type

//例如count()
template<class T,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;
}

对于difference_type也有对原生指针的两个特化版本。

3.4.3 reference type

在C++中,函数要传回左值,都是以by reference的方式进行。

3.4.4 pointer type

传回一个左值,令它代表p所指之物的地址。

ListIter class中,我们可以看到

Item& operator*() const { return *ptr; }
Item* operator&() const { return  ptr; }

Item&ListIterreference type,而Item*便是pointer type

reference typepointer type有对原生指针的两个特化版本。

3.4.5 iterator_category

根据移动特性与施行操作,迭代器被分为5类:
* input iterator:这种迭代器所指的对象,不允许外界改变,只读。
* output iterator:只写。
* forward iterator:允许“写入型”算法在此迭代器所形成的的区间上进行读写操作。
* bidirectional iterator:可双向移动。某些算法需要逆行走访某个迭代器区间,可以使用bidirectional iterators。
* random access iterator:前四种迭代器只供应某一部分指针算术能力,第五种则涵盖所有指针算术能力。
这五种迭代器是包含关系,但是在重载时,这五种迭代器具有继承关系。
iteraor_category迭代器的目的就是萃取迭代器类型。
例如advance()函数中iterator_category将迭代器类型萃取出来,并根据相应型别,调用不同_advance(),使得效率最大。
iterator_category也具有原型指针的偏特化版本,但是两者都属于random access iterator
任何迭代器,类型永远落在“该迭代器所隶属之各种类型中,最强化的一个”。
对于_advance()中的各个版本,我们以迭代器的类型进行区分。
STL中是将五种迭代器分别定义为class:

struct input_iterator_tag{};
···
struct random_access_iterator_tag:public bidirectional iterator_tag{};

//这些class只用来作为标记,所以为空。

这样做的好处是:
* 有利于函数的重载。
* 通过继承,我们可以不必再写“单纯传递函数”,例如advanceforward iterator就可以省略,这样他就因为继承关系自动传递调用input iterator版本的advance函数。

STL中的distance()也是如此,可以根据不同迭代器类型,有不同的计算方式,带来不同的效率。

在使用“萃取器”的过程中,我们不必自行定义这五种内嵌相应型别,因为STL提供了一个iterator class,因此,我们可以这样写:

template<class Item>
struct ListIter:public std::iterator<std::forward_iterator_tag,Item>
{···};

3.7 __type_traits

iterator_traits用来萃取迭代器的特性,__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;
}

struct __true_type{};
struct __false_type{};
//用来标记真假,但是要返回带有真假性质的对象,因此定义为class

SGI STL中已经对各种基本类型定义了特化版本。这些基本类型都是POD type,所以成员的值全是__true_type
实例:

template<class T> inline void copy(T* source,T* destination,int n){
    copy(source,destination,n,
        typename __type_traits<T>::has_trivial_copy_constructor());
}

//拥有非平凡拷贝构造函数
template<class T>void copy(T* source,T* destination,int n,
    __false_type)
    {···}
//拥有平凡拷贝构造函数
template<class T>void copy(T* source,T* destination,int n,
    __true_type)
    {···}

如果我们想要在我们的程序中使用__type_traits,当__type_traits对自行定义的类型进行萃取特性时,大多数的编译器时=是做不到的,萃取出来的特性都是__false_type。所以我们必须设计一个适合我们定义的类型的特化版本。

猜你喜欢

转载自blog.csdn.net/u013635579/article/details/81257998