本文翻译自modern effective 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版本的cbegin
cend(container), //non-member版本的cend
targetVal); 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. 条目二十六《iterator优先于const_iterator、reverse_iterator以及const_reverse_iterator》

    条目二十六<iterator优先于const_iterator.reverse_iterator以及const_reverse_iterator> 这几个东西不是类型来的,而是不同的类,所 ...

  2. Item 21: 比起直接使用new优先使用std::make_unique和std::make_shared

    本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 让我们先从std::make_unique和std::make_s ...

  3. item 11: 比起private undefined function优先使用deleted function

    本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 如果你为其他开发者提供代码,并且你想阻止他们调用一个特定的函数,你 ...

  4. item 10: 比起unscoped enum更偏爱scoped enum

    本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 一般情况下,在花括号中声明一个name(包括变量名,函数名),这个 ...

  5. item 5: 比起显式的类型声明,更偏爱auto

    本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 啊,简单愉快的代码: int x; 等等,讨厌!我忘了初始化x,所 ...

  6. item 8: 比起0和NULL更偏爱nullptr

    本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 先让我们看一些概念:字面上的0是一个int,不是一个指针.如果C+ ...

  7. 读书笔记 effective c++ Item 13 用对象来管理资源

    1.不要手动释放从函数返回的堆资源 假设你正在处理一个模拟Investment的程序库,不同的Investmetn类型从Investment基类继承而来, class Investment { ... ...

  8. Item 9: 比起typedef更偏爱别名声明(alias declaration)

    本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 我确信我们都同意使用STL容器是一个好主意,并且我希望在Item ...

  9. Effective C++ Item 13 Use object to manage resources

    1. Always use object to manage resource! If you delete a pointer or release a handler manually by yo ...

随机推荐

  1. 测试系统工程师TSE的职责与培养

    测试系统工程师TSE的职责与培养 研发资深顾问 杨学明 如今,国内所有的研发型的公司都有测试部门,无论测试团队大小,都有测试组长,测试经理,测试工程师等头衔,但随着产品和业务的质量要求越来越高,产品的 ...

  2. 使用 new XMLHttpRequest() 制作下载文件进度条

    mui 进度控件使用方法: 检查当前容器(container控件)自身是否包含.mui-progressbar类: 当前容器包含.mui-progressbar类,则以当前容器为目标控件,直接显示进度 ...

  3. 第一篇-Html标签中head标签,body标签中input系列,textarea和select标签

    第十四周课程(1-12章节) HTML 裸体 CSS   穿华丽衣服 Javascript 动起来 一 HTML (20个标签) 1.我们的浏览器是socket客户端 2.一套规则,浏览器认识的规则 ...

  4. Servlet以及单例设计模式

    1.Servlet概述 a)Servlet,全城是Servlet Applet,服务器端小程序,是一个接口,定义了若干方法,要求所有的Servlet必须实现. b)Servlet用于接收客户端的请求, ...

  5. Vue.js 2.x:组件的定义和注册(详细的图文教程)

    本文最初发表于博客园,并在GitHub上持续更新前端的系列文章.欢迎在GitHub上关注我,一起入门和进阶前端. 以下是正文. 前言 什么是组件 组件: 组件的出现,就是为了拆分Vue实例的代码量的, ...

  6. A problem has been detected and windows has been shut down to prevent damage to your computer.他么啥意思?看这里!【蓝屏】

    A problem has been detected and windows has been shut down to prevent damage to your computer.  检测到问 ...

  7. mssql sqlserver 使用sql脚本获取群组后,按时间排序(asc)第一条数据的方法分享

    摘要: 下文讲述使用sql脚本,获取群组后记录的第一条数据业务场景说明: 学校教务处要求统计: 每次作业,最早提交的学生名单下文通过举例的方式,记录此次脚本编写方法,方便以后备查,如下所示: 实现思路 ...

  8. 初学ubuntu之文件权限权限

    今天接着做笔记,坚持学习下去. 文件权限修改命令,初学者看见这个命令之后总有些摸不着头脑,这命令里面用到了一些数字,我 自己也是,这次写一篇自己的认识.希望能够帮助到需要学习的人. 首先你可以通过 l ...

  9. python 常见函数的用法

    filter(function,ls) 函数包括两个参数,分别是function和list.该函数根据function参数返回的结果是否为真来过滤list参数中的项,最后返回一个新列表. 如: map ...

  10. mysql建表基本语法

    mysql添加约束的两种条件: ------表的内部添加(约束) 列名1 数据类型 (int) primary key auto_increment,---主键默认不能为空的 列名2 数据类型 not ...