CSDN博客

img AMin2001

Effective STL 条款26

发表于2004/3/5 13:47:00  1464人阅读

分类: C++

条款26:尽量用iterator代替const_iterator,reverse_iterator和const_reverse_iterator

正如你所知的,每个标准容器类都提供四种迭代器类型。对于container<T>而言,iterator的作用相当于T*,而const_iterator则相当于const T*(你可能也见过T const*这样的写法:它们意思一样[1])。增加一个iterator或者const_iterator可以在一个从容器开头趋向尾部的遍历中让你移动到容器的下一个元素。reverse_iterator与const_reverse_iterator同样相当于对应的T*和const T*,所不同的是,增加reverse_iterator或者const_reverse_iterator会在从尾到头的遍历中让你移动到容器的下一个元素。

让我向你演示两个东西。第一,看看vector<T>的insert和erase的样式:

iterator insert(iterator position, const T& x);
iterator erase(iterator position);
iterator erase(iterator rangeBegin, iterator rangeEnd);

每个标准容器都包含了和这差不多的函数,虽然返回类型因容器类型的不同而不同。需要注意的是:这些方法只接受iterator类型的参数,而不是const_iterator、reverse_iterator或const_reverse_iterator。总是iterator。虽然容器类支持四种迭代器类型,但其中的一种类型有着其他所没有的特权。那就是iterator,iterator比较特殊[2]

我要让你看的第二个东西是这张图,它显示了几种迭代器之间存在的转换关系:

图中显示了从iterator到const_iterator、从iterator到reverse_iterator和从reverse_iterator到const_reverse_iterator可以进行隐式转换。并且,reverse_iterator可以通过调用其base成员函数转换为iterator。const_reverse_iterator也可以类似地通过base转换成为const_iterator。一个图中无法显示的事实是:通过base得到的也许并非你所期待的iterator。我们将会在条款28中详细讨论这一点。

你应该发现了没有办法从一个const_iterator转换得到一个iterator,也无法从const_reverse_iterator得到reverse_iterator。这一点非常重要,因为这意味着如果你有一个const_iterator或者const_reverse_iterator,你会发现让南让它们和容器的一些成员函数合作。那些成员函数要求iterator,而你无法从const迭代器类型反过来得到iterator。当你需要指出插入位置或删除的元素时,const迭代器几乎没有用。

千万不要傻乎乎的宣称const迭代器一无是处。不,它们可以与算法默契配合,因为算法通常并不关心迭代器是什么类型,只要是适当的种类(category)就可以了,很多容器的成员方法也接受const迭代器。只有insert和erase的一些形式有些吹毛求疵。

我写的是如果你要指出插入的位置或删除的元素时,const迭代器“几乎”没有用。这暗示了并不是完全没用。那是真的。如果你找到了一个方法可以从const_iterator或const_reverse_iterator得到一个iterator,那么它们就有用。那经常是有可能的。但这种方法并不总是行得通,而且就算可行,完成的方法也很不直观,也很缺乏效率。这个主题将足够充实一个它自己的条款,所以如果你对这个细节感兴趣的话,请转到条款27。现在,我们已经有足够的理由相信应该尽量使用iterator取代const或者reverse类型的迭代器:

  • insert和erase的一些版本要求iterator。如果你需要调用这些函数,你就必须产生iterator,而不能用const或reverse iterators。
  • 不可能把const_iterator隐式转换成iterator,我们将会在条款27中讨论从一个const_iterator产生一个iterator的技术并不普遍适用,而且不保证高效。
  • 从reverse_iterator转换而来的iterator在转换之后可能需要相应的调整,在条款28中我们会讨论何时需要调整以及调整的原因。

所有这些东西联合起来就能看出,如果你尽量使用iterator代替const或reverse类型的迭代器,可以使得容器的使用更简单,更高效而且可以避免潜在的bug。

事实上,你可能会更多地面临在iterator与const_iterator之间的选择.iterator与reverse_iterator之间的选择显而易见——依赖于从前到后或从后到前的遍历。你可以选择你需要的一种,而且即使你选择了reverse_iterator,当你要调用需要iterator的容器成员函数时,你仍然可以通过base得到相应的iterator(可能需要一些调整,参见条款28)。

当在iterator和const_iterator之间作选择的时候,你有更充分的理由选择iterator,即使const_iterator同样可行而且即使你并不需要调用容器类的任何成员函数。其中的令人讨厌的原因包括iterator与const_iterator之间的比较。我希望我们都可以赞成这是合理的代码:

typedef deque<int>  IntDeque;   // typedef可以极大地简化
typedef IntDeque::iterator Iter;   // STL容器类和iterator
typedef IntDeque::const_iterator ConstIter;  // 的操作。
Iter  i;
ConstIter ci;
...      // 同一个容器
if (i == ci) ...     // 比较iterator和const_iterator

我们所做的只是同一个容器中两个迭代器之间的比较,这是STL中最基本的动作。唯一的变化是等号的一边的类型是iterator,而另一边的类型是const_iterator。这应该不是问题,因为iterator应该在比较之前隐式的转换成const_iterator,真正的比较应该在两个const_iterator之间进行。

对于设计良好的STL实现而言,情况确实如此。但对于其它一些实现,这段代码甚至无法通过编译。原因在于,这些实现将const_iterator的operator==作为const_iterator的一个成员函数而不是非成员函数。而问题的解决之道显得非常有趣:只要像这样交换两个iterator的位置:

if (ci==i)...     // 当上面比较无法
      // 通过编译时的解决方法

不仅是比较是否相等,只要你在同一个表达式中混用iterator和const_iterator(或者reverse_iterator和const_reverse_iterator),这样的问题就可能会出现。比如,当你试图在两个随机存取迭代器之间进行减法操作时:

if (i-ci >= 3) ...     // 如果i与ci之间至少有三个元素...

如果迭代器的类型不同,你的(正确的)代码可能会被(错误地)拒绝。你可以想见解决的方法(交换i和ci的位置),但这次,不只是用i-ci替换ci-i了:

if (ci+3 <= i) ...     // 当上面的代码无法
      // 通过编译时的解决方法

避免这类问题的最简单的方法是减少混用不同类型的迭代器的机会,换句话说,又回到了尽量用iterator代替const_iterator。从const correctness的角度来看(一个固然有价值的角度),仅仅为了避免一些潜在的STL实现的弊端(而且,这些弊端都有较为直接的解决途径)而抛弃const_iterator显得有欠公允。但综合考虑到iterator与一些容器类成员函数的粘连关系,从实践得出const_iterator没有iterator好用的结论是很难避免的。更何况,有时并不值得卷入const_iterator的麻烦中去。


[1]关于这个主题的完整的文章,请参考1999年2月《Embedded Systems Programming》上刊登的《const T vs. T const》,作者是Dan Saks。

[2]“iterator比较特殊”的原因并不清楚。HP的最早的STL实现包含了带有iterator参数的insert和erase,而这个设计问题在标准化的过程中并没有重新考虑。但是在以后,这可能会改变,因为程序库工作组发布了#180记录,说明了“这个问题在下一个标准版本中会作为综合回顾的const问题而被考虑”(C++程序库问题可以从http://anubis.dkuug.dk/jtc1/sc22/wg21/docs/lwg-defects.html看到)

0 0

相关博文

我的热门文章

img
取 消
img