一、advance函数介绍
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d);
advance函数是C++的一个库函数,该函数的功能是将迭代器向前或者向后移动n个位置。advance是一个函数模板。理论上,只要 iter+=d 就能实现迭代器的移动,但是只有随机访问迭代器才支持算数运算。那么库函数是怎么实现这个功能的呢。
首先,回顾一下迭代器的类型。
二、迭代器类型
共有5种迭代器类型:
1、Input iterators (输入迭代器)—— 只能向前移动,每次只能移动一个元素,只能读取迭代器所指向的元素,并且只能读一次。c++标准库的istream_iterator就是这种迭代器。
2、Output iterators(输出迭代器) —— 与Input iterators类似,但是这种迭代器用于写操作。ostream_iterators属于这种迭代器。
3、forward iterators(前向迭代器)——具有Input iterators和Output iterators的所有功能。并且能够读写多次。单链表迭代器就是这种迭代器。
4、Bidirectional iterators(双向迭代器)——具有forward iterators迭代器的所有功能。并且能够向后移动。双链表的迭代器就是这种迭代器。
5、random access iterators(随机访问迭代器)——具有Bidirectional iterators迭代器的所有功能。并且支持算数运算。
三、advance的实现思路
首先想到的是,不使用算数运算来实现,通过多次的自增运算符,来实现迭代器的移动,这样所有迭代器类型都支持自增操作,但是这样效率比较低。
理想的实现应该如下:
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d)
{
if (iter == 随机访问迭代器) {
iter += d; // use iterator arithmetic
} // for random access iters
else {
if (d >= 0) { while (d--) ++iter; } // use iterative calls to
else { while (d++) --iter; } // ++ or -- for other
} // iterator categories
}
我们需要知道iter是不是随机访问的迭代器。这就是traits的功能,能够让我们在编译的时候获取类型信息。
另外,advance还需要满足内置类型,比如移动指针。这样在自定义类型里面加入类型信息的方法也就行不通了,因为无法为内置类型增加信息。那么标准库是怎么实现的呢?
首先,对于每种迭代器,标准库都定义了一个标签结构体。
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 {};
之后,每个迭代器必须包含一个typedef,来定义iterator_category为以上5种类型之一。
例如:
template < ... > // template params elided
class deque {
public:
class iterator {
public:
typedef random_access_iterator_tag iterator_category;
...
}:
...
};
针对迭代器,标准库定义了一个模板。用来获取迭代器类型信息。
template<typename IterT> // template for information about
struct iterator_traits; // iterator types
iterator_traits对IterT::iterator_category又进行typedef定义。
template<typename IterT>
struct iterator_traits {
typedef typename IterT::iterator_category iterator_category;
...
};
对于内置类型,有进行了模板偏特化(partial template specialization)。
template<typename IterT> // partial template specialization
struct iterator_traits<IterT*> // for built-in pointer types
{
typedef random_access_iterator_tag iterator_category;
...
};
基于以上信息,对advance进行重新定义如下:
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d)
{
if (typeid(typename std::iterator_traits<IterT>::iterator_category) ==
typeid(std::random_access_iterator_tag))
{
iter += d;
}
...
}
但是上面的代码还是不能通过编译,主要是因为iter+=d这行代码并不是所有的迭代器都支持。并且,IterT的类型是在编译时就确定的,所以std::iterator_traits<IterT>::iterator_category可以在编译时候就确定。但是if语句是在运行时进行的判断。这是在浪费时间。
标准库通过函数过载来实现在编译阶段实现if语句的功能。
首先定义doAdvance模板函数。
template<typename IterT, typename DistT> // use this impl for
void doAdvance(IterT& iter, DistT d, // random access
std::random_access_iterator_tag) // iterators
{
iter += d;
}
template<typename IterT, typename DistT> // use this impl for
void doAdvance(IterT& iter, DistT d, // bidirectional
std::bidirectional_iterator_tag) // iterators
{
if (d >= 0) { while (d--) ++iter; }
else { while (d++) --iter; }
}
template<typename IterT, typename DistT> // use this impl for
void doAdvance(IterT& iter, DistT d, // input iterators
std::input_iterator_tag)
{
if (d < 0 ) {
throw std::out_of_range("Negative distance"); // see below
}
while (d--) ++iter;
}
最终定义advance模板函数如下:
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d)
{
doAdvance( // call the version
iter, d, // of doAdvance
typename // that is
std::iterator_traits<IterT>::iterator_category() // appropriate for
); // iter's iterator
}
四、总结
1、通过模板和模板特例化,traits class实现在编译阶段获取类型信息。
2、配合函数过载,能够实现编译阶段的if...else