Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1791619
  • 博文数量: 600
  • 博客积分: 10581
  • 博客等级: 上将
  • 技术积分: 6205
  • 用 户 组: 普通用户
  • 注册时间: 2008-11-06 10:13
文章分类
文章存档

2016年(2)

2015年(9)

2014年(8)

2013年(5)

2012年(8)

2011年(36)

2010年(34)

2009年(451)

2008年(47)

分类: C/C++

2009-09-19 12:02:36

distance和advance把const_iterator转化成iterator

条款26中指出有些容器成员函数只接受iterator作为参数,而不是const_iterator。那么,如果你只有一个const_iterator,而你要在它所指向的容器位置上插入新元素呢?也就是如何把const_iterator转化为iterator呢?因为正如条款26所解释的,并不存在从const_iteratoriterator之间的隐式转换,所以你必须成为这次行动的主角。

我知道你在想什么。你正在想,每当无路可走的时候,就举起大锤!。在C++的世界里,你的意思只能是:映射(casting)。这种想法很可耻。真不知道你是哪儿学来的。

让我们面对困扰在你面前的问题。看看当你把一个const_iterator映射为iterator时会发生什么:

typedef deque<int> IntDeque;  // typedef, 简化代码。

typedef IntDeque::iterator Iter;

typedef IntDeque::const_iterator ConstIter;

ConstIter ci;    // ciconst_iterator

...

Iter i(ci);    // 错误!没有从const_iterator

     // iterator隐式转换的途径

Iter i(const_cast<Iter>(ci));  // 仍是个错误!不能从const_iterator

     // 映射为iterator

这里只是以deque为例,但是用其它容器类——listsetmultisetmapmultimap甚至条款25描述的hash表容器[1]——的结果一样。使用映射的也许vectorstring的代码时能够编译,但这是我们马上要讨论的非常特殊的情形。

包含映射的代码不能通过编译的原因在于,对于这些容器而言,iteratorconst_iterator是完全不同的类。它们之间并不比stringcomplex具有更多的血缘关系。在两个毫无关联的类之间进行const_cast映射是荒谬的,所以reinterpret_caststatic_cast甚至C风格的映射也会导致同样的结果。

唉,不能编译的代码对于vectorstring容器来说也许能够通过编译。那是因为通常情况下大多数实现都会采用真实的指针作为那些容器的迭代器。就这种实现而言,vector::iteratorT*typedef,而vector::const_iteratorconst T*typedefstring::iteratorchar*typedef,而string::const_iteratorconst char*typedef。在这种实现的情况下,用const_castconst_iterator映射成iterator当然可以编译而且没有问题,因为const_iteratoriterator之间的const_cast映射被最终解释成const T*T*的映射。但是,即使是在这种实现中,reverse_iteratorconst_reverse_iterator也是真正的类,所以你仍然不能直接用const_castconst_reverse_iterator映射成reverse_iterator。而且,正如条款50解释的,这些实现通常只会在Release模式时才使用指针表示vectorstring的迭代器[2]。所有这些事实表明,把const迭代器映射为迭代器是病态的,即使是对vectorstring来说也时,因为移植性很值得怀疑。

如果你得到一个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());    // 初始化id.begin()

advance(i, distance(i, ci));  // 调整i,指向ci位置

     // (但请留意下面关于为什么

     // 在它编译前要修改的原因)

这种方法看上去非常简单,直截了当,也很让人吃惊吧。要得到与const_iterator指向同一位置的iterator,首先将iterator指向容器的起始位置,并且让它移到和const_iterator距离容器起始位置的偏移量一样的位置即可!这个任务得到了两个实用算法advancedistance的帮助,它们都在<iterator>中声明。distance返回两个指向同一个容器的iterator之间的距离;advance则用于将一个iterator移动指定的距离。如果ici指向同一个容器,那么表达式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

template<typename InputIterator>

typename iterator_traits<InputIterator>::difference_type

distance(InputIterator first, InputIterator last);

当遇到distance调用时,你的编译器需要根据使用的参数类型推断出InputIterator的类型。再来看看我所说的不太正确的distance调用:

advance(i, distance(i, ci));   // 调整i,指向ci位置

有两个参数传递给distanceicii的类型是Iter,即deque<int>::iteratortypedef。对编译器来说,这表明调用distanceInputIteratordeque<int>::iterator。但ciConstIter,即deque<int>::const_iteratortypedef。这表明那个InputIteratordeque<int>::const_iteratorInputIterator不可能同时有两种不同的类型,所以调用distance失败。一般会造成一些冗长的出错信息,可能会也可能不会说明是编译器无法得出InputIterator是什么类型。

要顺利地调用distance,你需要排除歧义。最简单的办法就是显式的指明distance调用的模板参数类型,从而避免编译器自己得出它们的类型:

advance(i, distance<ConstIter>(i, ci));

我们现在知道了怎么通过advancedistance获取const_iterator相应的iterator了。但另一个我们现在一直避开却很值的考虑的实际问题是:这个技巧的效率如何?答案很简单。取决于你所转换的究竟是什么样的迭代器。对于随机访问的迭代器(比如vectorstringdeque的)而言,这是常数时间的操作。对于双向迭代器(也就是,所有其它容器和包括hash容器的一些实现[3](参见条款25))而言,这是线性时间的操作。

因为它可能花费线性时间的代价来产生一个和const_iterator等价的iterator,并且因为如果不能访问const_iterator所属的容器这个操作就无法完成。从这个角度出发,也许你需要重新审视你从const_iterator产生iterator的设计。事实上那样的考虑帮助激发了条款26,它建议你当处理容器时尽量用iterator代替constreverse迭代器。


[1]两个最常见的基于hash表的STL容器实现来自于DinkumwareSGI。你可以从P.J.Plauger 199811月份的CUJ专栏《Hash表》中找到一个Dinkumware方法的概览。我所知道的唯一的SGI的实现方法的概览来自Effective STL的条款25,但它的接口的描述在

[2]当使用STLport的调试模式时会出现这种情况。你可以从上了解到STLport和它的调试模式。

[3]Dinkumwarehash容器提供了双向的迭代器。SGI的、STLport的和Metrowerkshash容器只提供了前向迭代器。

 

阅读(661) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~