Item 13: 比起iterator优先使用const_iterator
本文翻译自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的更多相关文章
- 条目二十六《iterator优先于const_iterator、reverse_iterator以及const_reverse_iterator》
条目二十六<iterator优先于const_iterator.reverse_iterator以及const_reverse_iterator> 这几个东西不是类型来的,而是不同的类,所 ...
- Item 21: 比起直接使用new优先使用std::make_unique和std::make_shared
本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 让我们先从std::make_unique和std::make_s ...
- item 11: 比起private undefined function优先使用deleted function
本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 如果你为其他开发者提供代码,并且你想阻止他们调用一个特定的函数,你 ...
- item 10: 比起unscoped enum更偏爱scoped enum
本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 一般情况下,在花括号中声明一个name(包括变量名,函数名),这个 ...
- item 5: 比起显式的类型声明,更偏爱auto
本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 啊,简单愉快的代码: int x; 等等,讨厌!我忘了初始化x,所 ...
- item 8: 比起0和NULL更偏爱nullptr
本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 先让我们看一些概念:字面上的0是一个int,不是一个指针.如果C+ ...
- 读书笔记 effective c++ Item 13 用对象来管理资源
1.不要手动释放从函数返回的堆资源 假设你正在处理一个模拟Investment的程序库,不同的Investmetn类型从Investment基类继承而来, class Investment { ... ...
- Item 9: 比起typedef更偏爱别名声明(alias declaration)
本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 我确信我们都同意使用STL容器是一个好主意,并且我希望在Item ...
- 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 ...
随机推荐
- 资深程序员整理出来的Python面试题
转载链接:https://www.cnblogs.com/fcxwz/p/9225791.html
- AIOps背景/所应具备技术能力分析(上)
本文篇幅较长,分为上,中,下,三个部分进行连载.内容分别为:AIOps 背景/所应具备技术能力分析(上),AIOps 常见的误解(中),挑战及建议(下). 前言 我大概是 5,6 年前开始接触 ITO ...
- web前端(5)—— 常用标签2
以下三个不仅是常用标签了,还非常重要,所以请务必好好看,重要性从高到低: 盒模型div div标签是最常用最重要的,它可以把web页面分割成很多的小块分别管理 测试代码: <!DOCTYPE h ...
- Foreach用法
循环语句是编程的基本语句,在C#中除了沿用C语言的循环语句外,还提供了foreach语句来实现循环.那么我要说的就是,在循环操作中尽量使用foreach语句来实现. 为了来更好地说明为什么要提倡使 ...
- Docker详细介绍安装与镜像制作和拉取
一.Docker是什么? 产生背景: 开发和运维之间因为环境不同和导致的矛盾(不同的操作系统.软件环境.应用配置等)DevOps 代码.系统.环境.配置等封装成镜像Image--->运维: 集群 ...
- xss挑战之旅wp
Level 1 - 180831 第一关很简单,开胃菜 payload: http://localhost/xss_game/level1.php?name=test123<script&g ...
- 如何解决make时报错crti. o: unrecognized relocation (0x2a) in section `.init
这个问题困扰了我好长时间,网上查了好长时间,这个问题的解决方法,就是将binultils升级到2.26. 造成这个问题的原因是gcc和binultils版本不匹配,gcc对应的版本较高,gcc编译后, ...
- Python-爬虫03:urllib.request模块的使用
目录 1. urllib.request的基本使用 1.1 urlopen 1.2. 用urlopen来获取网络源代码 1.3. urllib.request.Request的使用 2. User-A ...
- Spring事务管理2--声明式
简述 1.Spring 的声明式事务管理在底层是建立在 AOP 的基础上.其本质是在方法前后进行拦截,然后在目标方法开始之前创建一个事务,在执行这目标方法结束后,根据执行情况提交或进行回滚事务. 2. ...
- 【转】cookie如何共享到各个浏览器
可以考虑HTML5 localstorage, 点击查看原始尺寸 http://www.cnblogs.com/xiaowei0705/archive/2011/04/19/2021372.html也 ...