用distance和advance把const_iterator转化成iterator
有些容器成员函数只接受iterator作为参数,而不是const_iterator。那么,如果你只有一const_iterator,而你要在它所
指向的容器位置上插入新元素呢?也就是如何把const_iterator转化为iterator呢?并不存在从const_iterator到
iterator之间的隐式转换,所以你必须成为这次行动的主角。
我知道你在想什么。你正在想,“每当无路可走的时候,就举起大锤!”。在C++的世界里,你的意思只能
是:映射。这种想法很可耻。真不知道你是哪儿学来的。
让我们面对困扰在你面前的问题。看看当你把一个const_iterator映射为iterator时会发生什么:
包含映射的代码不能通过编译的原因在于,对于这些容器而言,iterator和const_iterator是完全不同的类。它们之间并不比string 和complex<float>具有更多的血缘关系。在两个毫无关联的类之间进行const_cast映射是荒谬的,所以 reinterpret_cast、static_cast甚至C风格的映射也会导致同样的结果。
唉,不能编译的代码对于vector和string容器来说也许能够通过编译。那是因为通常情况下大多数实现都会采
用真实的指针作为那些容器的迭代器。就这种实现而言,vector<T>::iterator是T*的typedef,而 vector<T>::const_iterator是const T*的typedef,string::iterator是char*的typedef,而string::const_iterator是const char*的typedef。在这种实现的情况下,用const_cast把const_iterator映射成iterator当然可以编译而且没有问 题,因为const_iterator与iterator之间的const_cast映射被最终解释成const T*到T*的映射。但是,即使是在这种实现中,reverse_iterator和const_reverse_iterator也是真正的类,所以你仍 然不能直接用const_cast把const_reverse_iterator映射成reverse_iterator。而且,这些实现通常只会在 Release模式时才使用指针表示vector和string的迭代器。所有这些事实表明,把const迭代器映射为迭代器是病态的,即使是对 vector和string来说也是,因为移植性很值得怀疑。
如果你得到一个const_iterator并且可以访问它所指向的容器,那么有一种安全的、可移植的方法获取它所对
应的iterator,而且,用不着陷入类型系统的转换。下面是解决思路的本质,虽然在它编译前还要稍作修
改:
这种方法看上去非常简单,直截了当,也很让人吃惊吧。要得到与const_iterator指向同一位置的iterator,首先将iterator指向 容器的起始位置,然后把它向前移到和const_iterator距离容器起始位置的偏移量一样的位置即可!这个任务得到了两个函数模板advance和 distance的帮助,它们都在<iterator>中声明。distance返回两个指向同一个容器的iterator之间的距离; advance则用于将一个iterator移动指定的距离。如果i和ci指向同一个容器,那么表达式advance(i, distance(i, ci))会将i移动到与ci相同的位置上。
如果这段代码能够通过编译,它就能完成这种转换任务。但似乎事情并不那么顺利。想知道为什么,先来看
看distance的定义:
当遇到distance调用时,你的编译器需要根据使用的实参类型推断出InputIterator的类型。再来看看我所说的不太正确的distance调用:
要顺利地调用distance,你需要排除歧义。最简单的办法就是显式的指明distance调用的模板参数类型,从而避免编译器自己得出它们的类型:
我们现在知道了怎么通过advance和distance获取const_iterator相应的iterator了。但另一个我们现在一直避开却很值的 考虑的实际问题是:这个技巧的效率如何?答案很简单。取决于你所转换的究竟是什么样的迭代器。对于随机访问的迭代器(比如vector、string和 deque的)而言,这是常数时间的操作。对于双向迭代器(也就是,所有其它容器和包括散列容器的一些实现)而言,这是线性时间的操作。因为它可能花费线 性时间的代价来产生一个和const_iterator等价的iterator,并且因为如果不能访问const_iterator所属的容器这个操作就 无法完成。从这个角度出发,也许你需要重新审视你从const_iterator产生iterator的设计。
我知道你在想什么。你正在想,“每当无路可走的时候,就举起大锤!”。在C++的世界里,你的意思只能
是:映射。这种想法很可耻。真不知道你是哪儿学来的。
让我们面对困扰在你面前的问题。看看当你把一个const_iterator映射为iterator时会发生什么:
typedef deque<int> IntDeque; // 方便的typedef
typedef IntDeque::iterator Iter;
typedef IntDeque::const_iterator ConstIter;
ConstIter ci; // ci是const_iterator
Iter i(ci); // 错误!没有从const_iterator到iterator隐式转换的途径
Iter i(const_cast<Iter>(ci)); // 仍是个错误!不能从const_iterator映射为iterator!
这里只是以deque为例,但是用其它容器类——list、set、multiset、map、multimap甚至散列表容器的结果一样。使用映射的行也许在vector或string的代码时能够编译,但这是我们马上要讨论的非常特殊的情形。typedef IntDeque::iterator Iter;
typedef IntDeque::const_iterator ConstIter;
ConstIter ci; // ci是const_iterator
Iter i(ci); // 错误!没有从const_iterator到iterator隐式转换的途径
Iter i(const_cast<Iter>(ci)); // 仍是个错误!不能从const_iterator映射为iterator!
包含映射的代码不能通过编译的原因在于,对于这些容器而言,iterator和const_iterator是完全不同的类。它们之间并不比string 和complex<float>具有更多的血缘关系。在两个毫无关联的类之间进行const_cast映射是荒谬的,所以 reinterpret_cast、static_cast甚至C风格的映射也会导致同样的结果。
唉,不能编译的代码对于vector和string容器来说也许能够通过编译。那是因为通常情况下大多数实现都会采
用真实的指针作为那些容器的迭代器。就这种实现而言,vector<T>::iterator是T*的typedef,而 vector<T>::const_iterator是const T*的typedef,string::iterator是char*的typedef,而string::const_iterator是const char*的typedef。在这种实现的情况下,用const_cast把const_iterator映射成iterator当然可以编译而且没有问 题,因为const_iterator与iterator之间的const_cast映射被最终解释成const T*到T*的映射。但是,即使是在这种实现中,reverse_iterator和const_reverse_iterator也是真正的类,所以你仍 然不能直接用const_cast把const_reverse_iterator映射成reverse_iterator。而且,这些实现通常只会在 Release模式时才使用指针表示vector和string的迭代器。所有这些事实表明,把const迭代器映射为迭代器是病态的,即使是对 vector和string来说也是,因为移植性很值得怀疑。
如果你得到一个const_iterator并且可以访问它所指向的容器,那么有一种安全的、可移植的方法获取它所对
应的iterator,而且,用不着陷入类型系统的转换。下面是解决思路的本质,虽然在它编译前还要稍作修
改:
typedef deque<int> IntDeque; // 和以前一样
typedef IntDeque::iterator Iter;
typedef IntDeque::const_iterator ConstIter;
IntDeque d;
ConstIter ci;
// 让ci指向d
Iter i(d.begin()); // 初始化i为d.begin()
advance(i, distance(i, ci)); // 把i移到指向ci位置(但请留意下面关于为什么在它编译前要调整的原因)
typedef IntDeque::iterator Iter;
typedef IntDeque::const_iterator ConstIter;
IntDeque d;
ConstIter ci;
// 让ci指向d
Iter i(d.begin()); // 初始化i为d.begin()
advance(i, distance(i, ci)); // 把i移到指向ci位置(但请留意下面关于为什么在它编译前要调整的原因)
这种方法看上去非常简单,直截了当,也很让人吃惊吧。要得到与const_iterator指向同一位置的iterator,首先将iterator指向 容器的起始位置,然后把它向前移到和const_iterator距离容器起始位置的偏移量一样的位置即可!这个任务得到了两个函数模板advance和 distance的帮助,它们都在<iterator>中声明。distance返回两个指向同一个容器的iterator之间的距离; advance则用于将一个iterator移动指定的距离。如果i和ci指向同一个容器,那么表达式advance(i, distance(i, ci))会将i移动到与ci相同的位置上。
如果这段代码能够通过编译,它就能完成这种转换任务。但似乎事情并不那么顺利。想知道为什么,先来看
看distance的定义:
template<typename InputIterator>
typename iterator_traits<InputIterator>::difference_type distance(InputIterator first, InputIterator last);
不要被这个函数的长达56个字符的返回类型卡住,也不用理会difference_type是什么东西。取而代之的是,把注意力集中在参数的类型InputIterator:typename iterator_traits<InputIterator>::difference_type distance(InputIterator first, InputIterator last);
template<typename InputIterator>
typename iterator_traits<InputIterator>::difference_type
distance(InputIterator first, InputIterator last);
typename iterator_traits<InputIterator>::difference_type
distance(InputIterator first, InputIterator last);
当遇到distance调用时,你的编译器需要根据使用的实参类型推断出InputIterator的类型。再来看看我所说的不太正确的distance调用:
advance(i, distance(i, ci)); // 调整i,指向ci位置
有
两个参数传递给distance,i和ci。i的类型是Iter,即deque<int>::iterator的typedef。对编译器来
说,这表明调用distance的InputIterator是deque<int>::iterator。但ci是ConstIter,即
deque<int>::const_iterator的typedef。这表明那个InputIterator是deque<
int>::const_iterator。InputIterator不可能同时有两种不同的类型,所以调用distance失败。一般会造成一
些冗长的出错信息,可能会也可能不会说明是编译器无法得出InputIterator是什么类型。要顺利地调用distance,你需要排除歧义。最简单的办法就是显式的指明distance调用的模板参数类型,从而避免编译器自己得出它们的类型:
advance(i, distance<ConstIter>(i, ci));
我们现在知道了怎么通过advance和distance获取const_iterator相应的iterator了。但另一个我们现在一直避开却很值的 考虑的实际问题是:这个技巧的效率如何?答案很简单。取决于你所转换的究竟是什么样的迭代器。对于随机访问的迭代器(比如vector、string和 deque的)而言,这是常数时间的操作。对于双向迭代器(也就是,所有其它容器和包括散列容器的一些实现)而言,这是线性时间的操作。因为它可能花费线 性时间的代价来产生一个和const_iterator等价的iterator,并且因为如果不能访问const_iterator所属的容器这个操作就 无法完成。从这个角度出发,也许你需要重新审视你从const_iterator产生iterator的设计。