В чем причина cbegin/cend?


интересно, почему cbegin и cend были введены в C++11?

каковы случаи, когда вызов этих методов отличается от перегрузок const begin и end?

6 162

6 ответов:

это довольно просто. Скажем, у меня есть вектор:

std::vector<int> vec;

я заполняю его некоторыми данными. Тогда я хочу получить некоторые итераторы к нему. Может быть, передать их по кругу. Может быть, чтобы std::for_each:

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

В C++03, SomeFunctor был свободен, чтобы иметь возможность изменить параметр, который он получает. Конечно,SomeFunctor может принимать параметр по значению или const&, но нет обеспечить что он делает. Не без того, чтобы сделать что-то глупое вроде это:

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

теперь мы вводим cbegin/cend:

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

теперь у нас есть синтаксические гарантии, что SomeFunctor невозможно изменить элементы вектора (без const-cast, конечно). Мы явно получаем const_iterator s, а значит SomeFunctor::operator() будет вызван с const int &. Если он принимает его параметры как int & в C++ выдаст ошибку компилятора.


в C++17 имеет более элегантное решение этой проблемы: std::as_const. Ну, на по крайней мере, это элегантно при использовании диапазона на основе for:

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

это просто возвращает const& объекту это предоставляется.

помимо того, что сказал Николя Болас в ответ рассмотрим новый auto ключевые слова:

auto iterator = container.begin();

С auto, нет никакого способа, чтобы убедиться, что begin() возвращает постоянный оператор для непостоянной ссылки на контейнер. Так что теперь вы делаете:

auto const_iterator = container.cbegin();

возьмите это как практический usecase

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

задание не выполняется, так как it это nonconst итератора. Если бы вы изначально использовали cbegin, итератор имел бы правильный тип.

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

так что программист может непосредственно получить const_iterator даже из a неконстантный контейнер

они привели такой пример

vector<MyType> v;

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

однако, когда обход контейнера предназначен только для проверки, обычно предпочтительно использовать const_iterator для того, чтобы чтобы разрешить компилятору диагностировать с const-корректность нарушения

обратите внимание, что в рабочем документе также упоминаются шаблоны адаптеров, которые теперь были доработаны как std::begin() и std::end() и это также работает с собственными массивами. Соответствующее std::cbegin() и std::cend() любопытно отсутствуют на данный момент, но они также могут быть добавлены.

просто наткнулся на этот вопрос... Я знаю, что это уже ответ, и это просто боковой узел...

auto const it = container.begin() это другой тип затем auto it = container.cbegin()

на int[5] (используя указатель, который, как я знаю, не имеет метода begin, но хорошо показывает разницу... но будет работать в C++14 для std::cbegin() и std::cend(), что, по сути, то, что нужно использовать, когда он здесь)...

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 и const_iterator имеют отношение наследования и неявное преобразование происходит при сравнении или присвоении другому типу.

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)
{
    // ...
}

используя cbegin() и cend() увеличит производительность в этом случае.

for (std::vector<T>::const_iterator it=MyVector.cbegin(); it!=MyVector.cend(); ++it)
{
    // ...
}