本文翻译自《effective modern C++》,由于水平有限,故无法保证翻译完全正确,欢迎指出错误。谢谢!

博客已经迁移到这里啦

STL中的const_iterator等价于pointers-to-const(指向const值的指针)。它们指向的值不能被修改。使用const的标准做法是,每当你不需要修改iterator指向的值的时候,你都应该使用const_iterator。

这对C++98和C++11来说都是对的,但是在C++98中,const_iterator只能算勉强支持。我们无法简单地创建它们,并且一旦你创建了一个const_iterator,你使用的范围就被限制了。举个例子,假设你想要找到std::vector中的第一个1983(用“C++”替代“C with Classes”来作为名字的那一年),并且在那个位置插入一个1998(那一年,第一个IOS C++标准被采用)。如果vector中没有1983,插入的位置应该是vector的最后面。在C++98中,使用iterator来实现,这很简单:

std::vector<int> values;...std::vector<int>::iterator it =std::find(values.begin(), values.end(), 1983);
values.insert(it, 1998);

但是iterator在这不是最合适的选择,因为这段代码从来没有修改iterator指向的东西。把代码修改成const_iterator的版本“应该”很简单,但是在C++98中却不简单。这里有一种方法,从概念上来说是可靠的,但是它还是不正确的:

typedef std::vector<int>::iterator IterT;           //typedef
typedef std::vector<int>::const_iterator ConstIterT;std;;vector<int> values;...ConstIterT ci = std::find(static_cast<ConstIterT>(values.begin()),static_cast<ConstIterT>(values.end()),1983);values.insert(static_cast<IterT>(ci), 1998);        //可能无法编译,详情看下面

typedef不是必须的,但是他们让代码中的cast更容易写一些。(如果你对于我为什么用typedef代替Item 9中推荐的别名声明(alias declaration),这是因为,这个例子展示的是C++98的代码,而别名声明(alias declaration)是C++11中的新特性。)

在std::find调用中使用cast是因为values是一个non-const容器,然后在C++98中,这里没有简单的办法从non-const容器中获取一个const_iterator。cast不是必须的,因为用别的方式来获取const_iterator也是可能的(比如,你可以把values绑定到一个reference-to-const变量(就是const T&类型的值),然后在你的代码中用那个值代替values就可以了),但是不管通过哪种方式,通过一个non-const容器,获取它的const_iterator的过程都是很曲折的。

一旦你得到了const_iterator,事情变得更加糟糕了,因为在C++98中,只有iterator才能给插入(insertion)及删除(erasure)“定位”。const_iterator是不被接受的。这就是为什么,在上面的代码中,我把const_iterator(我好不容器从std::find中得到的)转换成了iterator(传入一个const_iterator给insert将无法编译)。

说实话,我给出的代码可能也无法编译,因为即使使用static_cast(甚至是众所周知的杀手锏reinterpret_cast),使const_iterator转换成iterator也是无法移植的。(这不是C++98的限制,在C++11中,也是这样的。无论它看起来多像是可移植的,const_iterator都不能简单地转换到iterator。)这里有一些可移植的方法来产生一个iterator(指向const_iterator指向的地方),但是他们都很复杂,不通用,并且不值得在本书中讨论。除此之外,我希望我的观点能清楚地向你传达:const_iterator在C++98中是个大麻烦,它们不值得使用。最后,开发人员都尽量不使用const,只在必要的情况下使用它,而且在C++98中,const_iterator太不实用了。

在C++11中,一切都变了。现在const_iterator已经变得容易获得以及容易使用了。容器(即使是non-const容器)的成员函数cbegin和cend产生一个const_iterator,并且原本在STL中,只使用iterator定位(比如,inset和erase)的成员函数现在也能使用const_iterator来定位了。把最初使用iterator的C++98版本的代码修改成使用const_iterator的C++11版本的代码真是太简单了:

std::vector<int> values;...auto it = std::find(values.cbegin(), values.cend(), 1983);    values.insert(it, 1998);

现在,代码用上了实用的const_iterator。

在C++11中,对于const_iterator的支持,唯一不足的情况就是在你想写一个最大限度的通用库的时候。比起让客户使用成员函数,这样的库代码需要考虑为容器和“类容器”提供non-member版本的begin和end(加上cbegin,cend,rbegin等)。举个例子,为了built-in数组需要这么做,为了一些只提供接口(包含一些函数)的三方库也要这么做。因此最大限度的通用库需要提供non-member版本的函数,而不是去假设所有“容器”都有成员函数。

举个例子,我们能把我们讨论的东西添加到findAndInsert模板中,像下面这样写:

template<typename C, typename V>
void findAndInsert(C& container,                const V& targetVal,const V& insertVal)
{using std::cbegin;using std::cend;auto it = std::find(cbegin(container),      //non-member版本的cbegincend(container),        //non-member版本的cendtargetVal);container.insert(it, insertVal);
}

这在C++14中工作得很好,但是,很遗憾,在C++11却无法很好地工作。由于制定标准时的疏忽,C++11只添加了non-member版本的begin和end函数,但是他们没有添加相应的cbegin,cend,rbegin,rend,crbegin,crend。C++14更正了这个问题。

如果你使用C++11,你又想写出最大限度的通用代码,并且在你使用的库中,没有一个库提供那些被遗漏的cbegin(non-member版本的)。那么朋友,你可以轻松地写出你自己的实现,举个例子,这里有一个non-member版本的cbegin的实现:

template<class C>
auto cbegin(const C& container)->decltype(std::begin(container))
{return std::begin(container);               //看下面的解释
}

看到non-member版本的cbegin没有调用member版本的cbegin,你觉得很奇怪是吧?我也觉得奇怪,但是跟着代码看下来。cbegin模板接受任何类型的参数来表示一个“类容器”(C),并且它通过它的reference-to-const形参(container)来使用实参。如果C是一个普通的容器类型(比如,一个std::vector),container将成为一个指向const容器的引用(也就是,const std::vector&)。用const容器调用non-member版本的begin函数(由C++11提供)就能产生一个const_iterator,并且这个iterator就是这个模板的返回值。用这样的方式来实现的优点是,对于那些提供了begin成员函数,但是没有提供cbegin成员函数的容器,能更好地工作(在C++11的non-member版本的begin中,会调用这个容器的begin成员函数)。因此,你能对只提供begin成员函数的容器,使用这个non-member版本的cbegin。

如果C是一个built-in数组类型,这个模板也能工作。在这种情况下,container成为一个指向const数组的引用。C++11在non-member版本的begin中,为数组提供了一个特殊的版本,这个版本的begin返回一个指向数组中第一个元素的指针。一个const数组的元素是const的,所以non-member版本的begin为const数组返回一个point-to-const的指针,并且事实上,一个point-to-const的指针对于数组来说就是一个const_iterator。(为了深入了解一个模板怎么为built-in数组特殊化,请看Item 1中,以指向数组的引用为参数的template类型推导的讨论。)

但是话说回来,这个Item的重点是,鼓励你,每当你能使用const_iterator时,就去使用它。最初的动机是,只要有必要,就要使用const,但是在C++11之前的C++98中,配合iterator来使用const很不实用。而在C++11中,它非常实用,并且C++14填了少量C++11遗留下来的坑(一小部分未实现的东西)。

            你要记住的事
  • 比起iterator优先使用const_iterator
  • 在最大限度的通用代码中,比起成员函数,优先使用non-member版本的begin,end,rbegin等等。

Item 13: 比起iterator优先使用const_iterator相关推荐

  1. Item 13: Prefer const_iterators to iterators.

    Item 13: Prefer const_iterators to iterators. Effective Modern C++ Item 13 的学习和解读. STL 中 const_itera ...

  2. Item 13 Minimize the accessibility of classes and members

    区分好的模块和不好的模块最重要的因素是看这个模块对于其他模块而言是否隐藏内部数据和其他细节.好的模块会把所有细节隐藏起来,把API和实现隔离开来,模块之间用API通信.这就是information h ...

  3. ES6之路第十三篇:Iterator和for...of循环

    Iterator(遍历器)的概念 JavaScript 原有的表示"集合"的数据结构,主要是数组(Array)和对象(Object),ES6 又添加了Map和Set.这样就有了四种 ...

  4. [C++]Deque with iterator实现细节

    https://blog.csdn.net/stary_yan/article/details/51601523 Deque with iterator实现细节 一.deque的中控器 deque是连 ...

  5. Effective C# 原则13:用静态构造函数初始化类的静态成员(译)

    Effective C# 原则13:用静态构造函数初始化类的静态成员 Item 13: Initialize Static Class Members with Static Constructors ...

  6. 设计模式学习总结-迭代器模式(Iterator Pattern)

    问题: 在面向对象的软件设计中,经常会遇到一些聚集对象,按一定的顺序的遍历访问问题,这个遍历算法究竟应该由谁来实现呢,聚集对象本身?这样聚集对象承受了过多的功能,不仅要维护聚集对象内的元素,还要提供遍 ...

  7. Design Pattern - Iterator(C#)

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! Defi ...

  8. 读书笔记 effective c++ Item 49 理解new-handler的行为

    1. new-handler介绍 当操作符new不能满足内存分配请求的时候,它就会抛出异常.很久之前,它会返回一个null指针,一些旧的编译器仍然会这么做.你仍然会看到这种旧行为,但是我会把关于它的讨 ...

  9. 读书笔记 effective c++ Item 18 使接口容易被正确使用,不容易被误用

    1. 什么样的接口才是好的接口 C++中充斥着接口:函数接口,类接口,模板接口.每个接口都是客户同你的代码进行交互的一种方法.假设你正在面对的是一些"讲道理"的人员,这些客户尝试把 ...

最新文章

  1. java中的後綴表達式_求Java堆栈,将中缀算术表达式转换成后缀表达式。
  2. Python对自定义离散点进行指定多项式函数拟合
  3. Windows 10 使用 Kali Linux子系统
  4. Oracle rowid和rownum的区别
  5. Linux内存管理 【转】
  6. SQL Server中追踪器Trace的介绍和简单使用-----(转)
  7. 3198元起!魅族16s开启预约:4月28日全渠道首发
  8. 大数据技术架构都有哪些变化
  9. 【报错】table burner has no column named USER (code 1): , while compiling: INSERT INTO burner(USER,YELL
  10. Ajax用法返回Json
  11. 导入从postman导出的json接口文件,并设置全局变量
  12. 遗传算法(三)——适应度与选择
  13. java set for循环_详解Java中list,set,map的遍历与增强for循环
  14. 正确卸载IE8并恢复IE6的两种方法
  15. 关于win 10电脑连接手机热点自动断开的问题
  16. mac悬浮窗_Mac OS 悬浮窗口,并且可以保持在全屏的其他应用上。
  17. android-ProGuard混淆
  18. 语音芯片如何选型?这篇文章告诉你
  19. iOS 底层探索篇 —— KVC 底层原理
  20. Parser-Free Virtual Try-on via Distilling Appearance Flows - 基于外观流提取的免解析器虚拟试穿

热门文章

  1. 利用python对批量修改文件名
  2. Oracle SCN详解
  3. DPDK官方文档说明
  4. 面试时,我说谎了——Leo网上答疑44
  5. 设计模式之禅-策略模式
  6. Camtasia 2022新版本发布CS喀秋莎2022功能亮点
  7. 计算机术语native版本,HiNative电脑版
  8. 数据结构学习记录——哈夫曼树(什么是哈夫曼树、哈夫曼树的定义、哈夫曼树的构造、哈夫曼树的特点、哈夫曼编码)
  9. 第九章 Maximum Variance Unfolding (MVU)
  10. 肖博高考数学二轮复习方法之圆锥曲线 解题策略附带题型解析