В чем причина cbegin/cend?
интересно, почему cbegin
и cend
были введены в C++11?
каковы случаи, когда вызов этих методов отличается от перегрузок const 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-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) { // ... }