前言
(建议将本节与Effective STL Item26、27结合阅读)
理论上来说,当你不需要改变迭代器所指对象时,你应当尽量使用const_iterator,这条规则对于C++98与C++11都适用。但实际上在C++98中,使用const_iterator会面临处处受限的窘境。
问题实例
举例而言,假若你希望在一个vector<int>中搜索1983第一次出现的位置,并在其位置插入一个1998,如果当前vector内不存在1983,则在vector尾端插入。使用C++98中的iterator可以轻松完成上述操作:1
2
3
4std::vector<int> values;
…
std::vector<int>::iterator it = std::find(values.begin(),values.end(), 1983);
values.insert(it, 1998);
由于我们并没有改变iterator指向的元素,理论上我们应当将其改为const_iterator。话虽如此,但在C++98中却并不简单,下面展示了一个似是而非的修正版本:1
2
3
4
5
6
7
8typedef std::vector<int>::iterator IterT;
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); // may not compile
find函数中使用了强制转换,原因在于:在C++98中,从一个non-const容器中获取const_iterator并非易事。当然了,也不一定非要使用强制转换,你可以定义一个常量引用指向values,然后在源代码中以该常量引用替换values。但不管怎么说,从获取一个指向non-const容器内部元素的const_iterator总是很麻烦。
即使你真的获取到了const_iterator,事情也未必会发展地如你所愿。在C++98中,插入和删除函数参数必须为iterator类型,这也就是上述代码最后千方百计将const_iterator转换回来的原因。不过,在上述代码的注释中标明强制转换可能无法编译(正确的转换方法与为何会无法编译可见Effective STL Item26、27)。
C++11中的const_iterator
C++11中一切都发生了改变,通过cbegin与cend成员函数,non-const容器也可以轻松获取const_iterator。此外,以往STL中需要以iterator标明位置的成员函数(例如insert、erase等等)现如今也可以用const_iterator作为参数,现在我们用C++11再次完成刚才所说的功能:1
2
3
4std::vector<int> values; // as before
…
auto it = std::find(values.cbegin(),values.cend(), 1983);
values.insert(it, 1998);
C++14中的const_iterator
尽管C++11中的const_iterator已经大幅度提高了我们的生产力,但其对于开发通用库程序时还是略显不足。这些程序可能会接收一个容器,返回其数据结构起始和结束的位置。我们认为真正的泛型应当以非成员函数的形式出现,而非成员函数。
假设我们需要泛型之前的findAndInsert程序:1
2
3
4
5
6
7template<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),cend(container),targetVal);
container.insert(it, insertVal);
}
遗憾的是上述程序只能在C++14中生效,原因在于C++11只添加了begin、end这两种非成员函数泛型版本,C++14才完成了cbegin、cend的加入。
但我们也可以稍加变动,在C++11中实现自己的cbegin:1
2
3
4template <class C>
auto cbegin(const C& container)->decltype(std::begin(container)){
return std::begin(container); // see explanation below
}
其工作原理为:如果实参为non-const,那么container将会成为一个const C&,并且对container调用begin将得到一个const_iterator。这种实现的一大优点在于对于那些带有begin成员函数却不带有cbegin成员函数的容器,你甚至可以直接在函数内部调用begin转换之。
如果C是一个内置数组,那么这个模板函数也能够正确地运行,因为container被推衍为一个const数组的引用,C++11的非成员函数begin会返回一个指向数组首元素的指针,又由于数组现为const,因此指针类型为一个指向const的指针,这与const_iterator是等价的。
总结
- 以const_iterator代替iterator。
- begin,end之类函数的非成员函数版本比其成员函数版本更加适合于泛型程序。