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

189

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

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

Андрей
источник

Ответы:

228

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

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_iterators, и поэтому SomeFunctor::operator()будем вызывать с const int &. Если он принимает параметры как int &, C ++ выдаст ошибку компилятора.


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

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

Это просто возвращает const&объект, который он предоставляет.

Николь Болас
источник
1
Я думал, что новый протокол был cbegin (vec), а не vec.cbegin ().
Каз Дракон
20
@ Kaz: нет std::cbegin/cendсвободных функций, которые std::begin/std::endсуществуют. Это был недосмотр комитета. Если бы эти функции существовали, это был бы способ их использования.
Николь Болас
20
Судя по всему, std::cbegin/cendбудет добавлено в C ++ 14. См. En.cppreference.com/w/cpp/iterator/begin
Ади Шавит
9
@NicolBolas for(auto &item : std::as_const(vec))эквивалентно for(const auto &item : vec)?
luizfls
9
@luizfls Да. Ваш код говорит, что элемент не будет изменен, если поставить constссылку. Ник рассматривает контейнер как const, поэтому autoвыводит constссылку. ИМО auto const& itemпроще и понятнее. Непонятно, почему std::as_const()здесь хорошо; Я вижу, что это было бы полезно при передаче чего-то не constобщего кода, где мы не можем контролировать используемый тип, но с помощью range- forмы можем, так что это просто кажется мне дополнительным шумом.
underscore_d
66

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

auto iterator = container.begin();

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

auto const_iterator = container.cbegin();
Стефан Маевский
источник
2
@allyourcode: не помогает. Для компилятора const_iteratorэто просто еще один идентификатор. Ни одна из версий не использует поиск обычных членов typedefs decltype(container)::iteratorили decltype(container)::const_iterator.
aschepler
2
@aschepler Я не понимаю ваше второе предложение, но я думаю, что вы пропустили «const» перед «auto» в моем вопросе. Независимо от того, к чему приходит auto, кажется, что const_iterator должен быть const.
allyourcode
26
@allyourcode: это даст вам итератор, который является постоянным, но он сильно отличается от итератора к постоянным данным.
aschepler
2
Есть простой способ убедиться, что вы получите const_iteratorс auto: Напишите шаблон вспомогательной функции, вызываемой make_constдля определения аргумента объекта.
Коломбо
17
Может быть, я просто больше не в мышлении C ++, но не вижу связи между понятиями «простой способ» и «написать шаблон вспомогательной функции». ;)
Стефан Маевский
15

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

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

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

Йоханнес Шауб - Литб
источник
8

С 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()как ни странно, отсутствуют на этот раз, но они также могут быть добавлены.

TemplateRex
источник
5

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

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
Крис Г.
источник
2

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)
{
    // ...
}
hkBattousai
источник
Мне потребовалось некоторое время, чтобы понять, что вы подразумеваете, что производительность сохраняется за счет исключения преобразования при инициализации и сравнении итераторов, а не популярный миф о том, что constглавное преимущество - это производительность (а это не так: это семантически правильный и безопасный код). Но, хотя у вас есть точка зрения, (А) autoделает это не проблема; (B) говоря о производительности, вы упустили главное, что вы должны были здесь сделать: кэшировать endитератор, объявив его копию в init-условии forцикла, и сравнить с этим, вместо того, чтобы получать новую копию с помощью значение для каждой итерации. Это сделает вашу точку зрения лучше. : P
underscore_d
@underscore_d constопределенно может помочь достичь более высокой производительности не из-за некоторого волшебства в самом constключевом слове, а потому, что компилятор может включить некоторые оптимизации, если он знает, что данные не будут изменены, что было бы невозможно в противном случае. Проверьте этот бит из выступления Джейсона Тернера для живого примера этого.
мозговой заговор
@brainplot Я не сказал, что не мог. Я сказал, что это не главное преимущество, и я думаю, что оно преувеличивается, когда реальным преимуществом является семантически правильный и безопасный код.
underscore_d
@underscore_d Да, я согласен с этим. Я просто сделал это явно, что constможет (почти косвенно) привести к повышению производительности; на тот случай, если кто-то прочитает это, возможно, подумает: «Я не буду беспокоиться о добавлении, constесли сгенерированный код никогда не будет затронут», что неверно.
мозговой заговор
0

это просто, 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 и обозначаем их как сценарий с постоянными итераторами - 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, которые возвращают постоянный итератор

ПАВАН КУМАР
источник