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