What is the reason behind cbegin/cend?

僤鯓⒐⒋嵵緔 提交于 2019-11-27 02:32:50
Nicol Bolas

It's quite simple. Say I have a vector:

std::vector<int> vec;

I fill it with some data. Then I want to get some iterators to it. Maybe pass them around. Maybe to std::for_each:

std::for_each(vec.begin(), vec.end(), SomeFunctor());

In C++03, SomeFunctor was free to be able to modify the parameter it gets. Sure, SomeFunctor could take its parameter by value or by const&, but there's no way to ensure that it does. Not without doing something silly like this:

const std::vector<int> &vec_ref = vec;
std::for_each(vec_ref.begin(), vec_ref.end(), SomeFunctor());

Now, we introduce cbegin/cend:

std::for_each(vec.cbegin(), vec.cend(), SomeFunctor());

Now, we have syntactic assurances that SomeFunctor cannot modify the elements of the vector (without a const-cast, of course). We explicitly get const_iterators, and therefore SomeFunctor::operator() will be called with const int &. If it takes it's parameters as int &, C++ will issue a compiler error.


C++17 has a more elegant solution to this problem: std::as_const. Well, at least it's elegant when using range-based for:

for(auto &item : std::as_const(vec))

This simply returns a const& to the object it is provided.

Stefan Majewsky

Beyond what Nicol Bolas said in his answer, consider the new auto keyword:

auto iterator = container.begin();

With auto, there's no way to make sure that begin() returns a constant operator for a non-constant container reference. So now you do:

auto const_iterator = container.cbegin();

Take this as a practical usecase

void SomeClass::f(const vector<int>& a) {
  auto it = someNonConstMemberVector.begin();
  ...
  it = a.begin();
  ...
}

The assignment fails because it is a nonconst iterator. If you used cbegin initially, the iterator would have had the right type.

From http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1674.pdf:

so that a programmer can directly obtain a const_iterator from even a non-const container

They gave this example

vector<MyType> v;

// fill v ...
typedef vector<MyType>::iterator iter;
for( iter it = v.begin(); it != v.end(); ++it ) {
    // use *it ...
}

However, when a container traversal is intended for inspection only, it is a generally preferred practice to use a const_iterator in order to permit the compiler to diagnose const-correctness violations

Note that the working paper also mentions adapter templates, that now have been finalized as std::begin() and std::end() and that also work with native arrays. The corresponding std::cbegin() and std::cend() are curiously missing as of this time, but they might also be added.

Just stumbled upon this question... I know it's alredy answerd and it's just a side node...

auto const it = container.begin() is a different type then auto it = container.cbegin()

the difference for int[5] (using pointer, which i know don't have the begin method but show nicely the difference... but would work in c++14 for std::cbegin() and std::cend(), which is essentially what one should use when it's here)...

int numbers = array[7];
const auto it = begin(numbers); // type is int* const -> pointer is const
auto it = cbegin(numbers);      // type is int const* -> value is const

iterator and const_iterator have inheritance relationship and an implicit conversion occurs when compared with or assigned to the other type.

class T {} MyT1, MyT2, MyT3;
std::vector<T> MyVector = {MyT1, MyT2, MyT3};
for (std::vector<T>::const_iterator it=MyVector.begin(); it!=MyVector.end(); ++it)
{
    // ...
}

Using cbegin() and cend() will increase performance in this case.

for (std::vector<T>::const_iterator it=MyVector.cbegin(); it!=MyVector.cend(); ++it)
{
    // ...
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!