获得迭代器的“性质”
最近在拜读侯捷先生的STL源码剖析,当读到traits技法时,感觉非常痛苦,读不太懂,于是想从网上搜一下关于这方面的知识,可是发现网上的解答并不能解决自己的疑惑,于是自己花了很长时间研究这个问题,下面跟大家分享一下我的理解。
首先要说的是,迭代器的特性,比如说迭代器的5个种类,Input Iterator、Output Iterator、Forward Iterator、Bidirectional Iterator、Random Access Iterator,这五种迭代器所拥有的性质是不一样的,比如说Random Access Iterator支持加n操作,通俗的说可以“跳”,而Input Iterator却不支持这种操作,后者只能++。
有了上面的铺垫就可以进入正文了,首先,迭代器是泛型算法与容器的桥梁,当算法对容器的数据进行操作时可能想要知道迭代器的某些性质,比如说,迭代器的类型,至于算法为什么想知道迭代器的类型,就像前面所说的那样,如果该迭代器是Random Acess Iterator类型的,那么所设计的算法可以直接支持加减n操作,比如说,我们所熟知的distance算法,它能够返回两个迭代器之间的距离,如果迭代器类型是Random Access Iterator,我们就直接写出以下操作:
template <class RandomAccessIterator>
inline iterator_traits<Random AccessIterator>::difference_type
_distance(RandomAccessIterator first,RandomAccessIterator last,random_access_iterator_tag)
{
return last-first;
}
这里我们只关心函数体内的部分,至于函数的参数我们暂不考虑,函数体内直接用last-first就得到了我们想要的距离,对比于此,我写出Input Iterator同样的函数体
while(first != last)
{
++first;
}
由此可以看出Input Iterator迭代器实现同样的算法需要遍历操作,如果我们的算法对于所有类型的迭代器一视同仁,那么我们的效率可能会大大降低,因为找不到适合各个迭代器的版本。
如何迭代器的“性质”
以上我们讲述了为什么要获得迭代器的性质,比如说种类性质,当然还有value type 性质等,它指的是迭代器所指的元素类型,那么我们如何来获得这些性质,我们如何才能知道迭代器是何种类,如何获得迭代器所指元素的类型。那么就要引入traits,即萃取操作。
如何能得到value type呢?我们可以想到用内嵌类型声明,typedef关键字,这样确实能得到我们想要的value type,但前提是迭代器是class type类型,然而并不是所有的迭代器都是class type类型,比如说native pointer,就是原生指针,那么该如何解决这个问题呢?
这就需要我们的“萃取机”了,我们定义一个萃取操作,将class type类型和原生指针分开做处理不就好了,而traits是一个类模板,如何才能将class type和原生指针区分出来呢,这个区分的操作就是偏特化,假如我们的traits用的,那么我们可以将原生指针定义为<T*>,这就是所谓的偏特化,通俗的说就是将一个模板的某个点给具体化,但不是全部,再举个例子,假如我们的类模板是,那么我们可以把int的单独做一个特化实现,其他的不管是什么再执行另一个操作,这里对int的操作就是偏特化,你可以先理解函数的重载,但并不是函数重载,我只能说有那么一点像,实际上,你可以通过函数重载来获得迭代器为何种类。加入我想知道迭代器it的种类,我可以有如下操作:
void print(InputIterator in)
{
cout<<"InputIterator";
}
void print(OutputIterator out)
{
cout<<"OutputIterator";
}
print(it);
以上只做解释用,并非完整代码,这样我们就能得到it的种类,编译器会为我们判断。
有了traits这把利刃,我们便可以把萃取得到的譬如迭代器的种类信息,所指元素类型信息作为泛型算法的另一个参数,这样我们设计的泛型算法的效率将会更加强大。