Skip to content

13 Prefer const_iterators to iterators

STL 中的 const_iterator 等价于指向 const 的指针,它们指向的值不会被修改。标准实践中尽可能的使用 const,因此也应该尽可能的使用 const_iterator

但是由于 C++98 中对 const_iterator 支持的很差劲,所以不好用。很难创建,即使创建出来了,能够使用的地方也非常有限。比如下面的例子,在 vector<int> 中寻找第一个 1983 出现的地方,在其后插入 1998。如果没有找到,那么在最后插入 1998。C++98 使用 iterator 的例子如下

std::vector<int> values;
std::vector<int>::iterator it = std::find(values.begin(), values.end(), 1983);
values.insert(it, 1998);

在这里,iterator 不是一个很好的选择,因为我们不打算修改它。在 C++98 中使用 const_iterator 非常繁琐,下面是可能可行的方法

typedef std::vector<int>::iterator IterT; // typedefs
typedef std::vector<int>::const_iterator ConstIterT;

std::vector<int> values;

ConstIterT ci =
    std::find(static_cast<ConstIterT>(values.begin()), // cast
              static_cast<ConstIterT>(values.end()),   // cast
              1983);

values.insert(static_cast<IterT>(ci), 1998); // may not compile; see below

typedef 不是必须的,如 Item 9 所言,更应该使用 using,不过那是 C++11 的语法而这是 C++98 的例子。

std::find 中有两次类型转化,原因是 values 不是 const 容器,没有很简单的方式获取 const_iterator。另一个可选的方法是将 values 绑定到一个 const 引用上,然后用这个引用替代 values。不管怎么说,都很麻烦。

即使获取了 const_iterator,但是 C++98 中 insert 只能接受 iterator 作为类型来指定位置。好不容易得到的 const_iterator,又不得不转化回 iterator

上面的代码可能无法通过编译,因为没有可移植的稳定的方法从 const_iteratoriteratorstatic_cast,甚至是 reinterpret_cast 都不行,即使到了 C++11 也不行,虽然这件事看起来挺合理。因为这种种限制,尽可能使用 const_iterator 这条原则在 C++98 时代不实际。

C++11 就完全不一样了。即使对于非 const 容器,cbegin,cend 也能返回 const_iterator 对象。STL 成员函数,比如 insert erase 也都接受 const_iterator 作为参数。这使得在 C++11 中使用 const_iterator 使用和 C++98 中使用 iterator 一样容易。

std::vector<int> values;                                   // as before
auto it = std::find(values.cbegin(), values.cend(), 1983); // use cbegin and cend
values.insert(it, 1998);

如果我们编写更通用的代码,需要考虑容器或者类容器对象只提供了非成员函数 begin end 等。其中一种情况就是原生的数组,或者是一些只提供非成员函数的类库。更通用的类库需要考虑这些。

之前的例子可以写成一个更通用的模板函数。

template <typename C, typename V>
void findAndInsert(C &container,       // in container, find first occurrence
                   const V &targetVal, // of targetVal, then insert insertVal
                   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);
}

Things to Remember

  • Prefer const_iterator to iterator.
  • In maximally generic code, prefer non-member versions of begin, end, rbegin, etc., over their member function counterparts.