条款13:优先使用const_iterator而不是iterator
===============
`const_iterator`在STL中等价于指向`const`的指针。被指向的数值是不能被修改的。标准的做法是应该使用`const`的迭代器的地方,也就是尽可能的在没有必要修改指针所指向的内容的地方使用`const_iterator`。
这对于C++98和C++11是正确,但是在C++98中,`const_iterator`s只有部分的支持。一旦有一个这样的迭代器,创建它们并非易事,使用也会受限。举一个例子,假如你希望从`vector<int>`搜索第一次出现的1983(这一年"C++"替换"C + 类"而作为一个语言的名字),然iterator后在搜到的位置插入数值1998(这一年第一个ISO C++标准被接受)。如果在vector中并不存在1983,插入操作的位置应该是vector的末尾。在C++98中使用`iterator`,这会非常容易:
```cpp
std::vector<int> values;
…
std::vector<int>::iterator it =
std::find(values.begin(),values.end(), 1983);
values.insert(it, 1998);
```
在这里`iterator`并不是合适的选择,因为这段代码永远都不会修改`iterator`指向的内容。重新修改代码,改成`const_iterator`s是不重要的,但是在C++98中,有一个改动看起来是合理的,但是仍然是不正确的:
```cpp
typedef std::vector<int>::iterator IterT; // typetypedef
std::vector<int>::const_iterator ConstIterT; // defs
std::vector<int> values;
…
ConstIterT ci =
std::find(static_cast<ConstIterT>(values.begin()), // cast
static_cast<ConstIterT>(values.end()), 1983); // cast
values.insert(static_cast<IterT>(ci), 1998); // 可能无法编译
// 参考后续解释
```
`typedef`并不是必须的,当然,这会使得代码更加容易编写。(如果你想知道为什么使用`typedef`而不是使用规则9中建议使用的别名声明,这是因为这个例子是C++98的代码,别名声明的特性是C++11的。)
在`std::find`中的强制类型转换是因为`values`是在C++98中是非`const`的容器,但是并没有比较好的办法可以从一个非`const`容器中得到一个`const_iterator`。强制类型转换并非必要的,因为可以从其他的办法中得到`const_iterator`(比如,可以绑定`values`到一个`const`的引用变量,然后使用这个变量代替代码中的`values`),但是不管使用哪种方式,从一个非`const`容器中得到一个`const_iterator`牵涉到太多。
一旦使用了`const_iterator`,麻烦的事情会更多,因为在C++98中,插入或者删除元素的定位只能使用`iterator`,`const_iterator`是不行的。这就是为什么在上面的代码中,我把`const_iterator`(从`std::find`中小心翼翼的拿到的)有转换成了`iterator`:`insert`给一个`const_iterator`会编译不过。
老实说,我上面展示的代码可能就编译不过,这是因为并没有合适的从`const_iterator`到`interator`的转换,甚至是使用`static_cast`也不行。甚至最暴力的`reinterpret_cast`也不成。(这不是C++98的限制,同时C++11也同样如此。`const_iterator`转换不成`iterator`,不管看似有多么合理。)还有一些方法可以生成类似`const_iterator`行为的`iterator`,但是它们都不是很明显,也不通用,本书中就不讨论了。除此之外,我希望我所表达的观点已经明确:`const_iterator`在C++98中非常麻烦事,是万恶之源。那时候,开发者在必要的地方并不使用`const_iterator`,在C++98中`const_iterator`是非常不实用的。
所有的一切在C++11中发生了变化。现在`const_iterator`既容易获得也容易使用。容器中成员函数`cbegin`和`cend`可以产生`const_iterator`,甚至非`const`的容器也可以这样做,STL成员函数通常使用`const_iterator`来进行定位(也就是说,插入和删除insert and erase)。修订原来的C++98的代码使用C++11的`const_iterator`替换原来的`iterator`是非常的简单的事情:
```cpp
std::vector<int> values; // 和之前一样
…
auto it = // use cbegin
std::find(values.cbegin(),values.cend(), 1983); // and cend
values.insert(it, 1998);
```
现在代码使用`const_iterator`非常的实用!
在C++11中只有一种使用`const_iterator`的短处就是在编写最大化泛型库的代码的时候。代码需要考虑一些容器或者类似于容器的数据结构提供`begin`和`end`(加上cbegin, cend, rbegin等等)作为非成员函数而不是成员函数。例如这种情况针对于内建的数组,和一些第三方库中提供一些接口给自由无约束的函数来使用。最大化泛型代码使用非成员函数而不是使用成员函数的版本。
- 出版者的忠告
- 致谢
- 简介
- 第一章 类型推导
- 条款1:理解模板类型推导
- 条款2:理解auto类型推导
- 条款3:理解decltype
- 条款4:知道如何查看类型推导
- 第二章 auto关键字
- 条款5:优先使用auto而非显式类型声明
- 条款6:当auto推导出非预期类型时应当使用显式的类型初始化
- 第三章 使用现代C++
- 条款7:创建对象时区分()和{}
- 条款8:优先使用nullptr而不是0或者NULL
- 条款9:优先使用声明别名而不是typedef
- 条款10:优先使用作用域限制的enmu而不是无作用域的enum
- 条款11:优先使用delete关键字删除函数而不是private却又不实现的函数
- 条款12:使用override关键字声明覆盖的函数
- 条款13:优先使用const_iterator而不是iterator
- 条款14:使用noexcept修饰不想抛出异常的函数
- 条款15:尽可能的使用constexpr
- 条款16:保证const成员函数线程安全
- 条款17:理解特殊成员函数的生成
- 第四章 智能指针
- 条款18:使用std::unique_ptr管理独占资源
- 条款19:使用std::shared_ptr管理共享资源
- 条款20:在std::shared_ptr类似指针可以悬挂时使用std::weak_ptr
- 条款21:优先使用std::make_unique和std::make_shared而不是直接使用new
- 条款22:当使用Pimpl的时候在实现文件中定义特殊的成员函数
- 第五章 右值引用、移动语义和完美转发
- 条款23:理解std::move和std::forward
- 条款24:区分通用引用和右值引用
- 条款25:在右值引用上使用std::move 在通用引用上使用std::forward
- 条款26:避免在通用引用上重定义函数
- 条款27:熟悉通用引用上重定义函数的其他选择
- 条款28:理解引用折叠
- 条款29:假定移动操作不存在,不廉价,不使用
- 条款30:熟悉完美转发和失败的情况
- 第六章 Lambda表达式
- 条款31:避免默认的参数捕捉
- 条款32:使用init捕捉来移动对象到闭包
- 条款33:在auto&&参数上使用decltype当std::forward auto&&参数
- 条款34:优先使用lambda而不是std::bind
- 第七章 并发API
- 条款35:优先使用task-based而不是thread-based
- 条款36:当异步是必要的时声明std::launch::async
- 条款37:使得std::thread在所有的路径下无法join
- 条款38:注意线程句柄析构的行为
- 条款39:考虑在一次性事件通信上void的特性
- 条款40:在并发时使用std::atomic 在特殊内存上使用volatile
- 第八章 改进
- 条款41:考虑对拷贝参数按值传递移动廉价,那就尽量拷贝
- 条款42:考虑使用emplace代替insert