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

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

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

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_iterator с, и, следовательно, 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();

Возьмите это в качестве практического использования

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

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

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

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

Они привели этот пример

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

Это просто, cbegin возвращает постоянный итератор, где begin возвращает только итератор

для лучшего понимания давайте рассмотрим два сценария здесь

сценарий - 1:

#include <iostream>
using namespace std;
#include <vector>
int main(int argc, char const *argv[])
{
std::vector<int> v;

for (int i = 1; i < 6; ++i)
{
    /* code */
    v.push_back(i);
}

for(auto i = v.begin();i< v.end();i++){
    *i = *i + 5;
}

for (auto i = v.begin();i < v.end();i++){
    cout<<*i<<" ";
}

return 0;
}

это будет работать, потому что здесь итератор i не является постоянным и может быть увеличен на 5

теперь давайте использовать cbegin и cend, обозначив их как сценарий постоянных итераторов - 2:

#include <iostream>
using namespace std;
#include <vector>
int main(int argc, char const *argv[])
{
std::vector<int> v;

for (int i = 1; i < 6; ++i)
{
    /* code */
    v.push_back(i);
}

for(auto i = v.cbegin();i< v.cend();i++){
    *i = *i + 5;
}

for (auto i = v.begin();i < v.end();i++){
    cout<<*i<<" ";
}

return 0;
}

это не сработает, потому что вы не можете обновить значение с помощью cbegin и cend, которые возвращают постоянный итератор

Другие вопросы по тегам