條款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