Jaki jest powód cbegin / cend?

188

Zastanawiam się, dlaczego cbegini cendzostały wprowadzone w C ++ 11?

Jakie są przypadki, gdy wywołanie tych metod różni się od stałych przeciążeń begini end?

Andrey
źródło

Odpowiedzi:

227

To całkiem proste. Powiedz, że mam wektor:

std::vector<int> vec;

Wypełniam je pewnymi danymi. Następnie chcę uzyskać iteratory. Może przekażę je. Może std::for_each:

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

W C ++ 03 można SomeFunctorbyło swobodnie modyfikować otrzymany parametr. Oczywiście, SomeFunctormoże przyjmować swój parametr według wartości lub według const&, ale nie ma sposobu, aby się upewnić . Nie bez robienia czegoś tak głupiego:

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

Teraz przedstawiamy cbegin/cend:

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

Teraz mamy gwarancje składniowe, które SomeFunctornie mogą modyfikować elementów wektora (oczywiście bez ograniczenia). Dostajemy jawnie const_iterators, i dlatego SomeFunctor::operator()będziemy wywoływani z const int &. Jeśli zajmie to parametry int &, C ++ spowoduje błąd kompilatora.


C ++ 17 ma bardziej eleganckie rozwiązanie tego problemu: std::as_const. Cóż, przynajmniej jest elegancki, gdy używasz zasięgu for:

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

To po prostu zwraca a const&do obiektu, który jest udostępniony.

Nicol Bolas
źródło
1
Myślałem, że nowy protokół to cbegin (vec) zamiast vec.cbegin ().
Kaz Dragon
20
@Kaz: Nie ma żadnych std::cbegin/cendbezpłatnych funkcji, które std::begin/std::endistnieją. Był to nadzór komitetu. Gdyby te funkcje istniały, byłby to na ogół sposób ich użycia.
Nicol Bolas,
20
Najwyraźniej std::cbegin/cendzostanie dodany w C ++ 14. Zobacz en.cppreference.com/w/cpp/iterator/begin
Adi Shavit
8
@NicolBolas jest for(auto &item : std::as_const(vec))równoważne z for(const auto &item : vec)?
luizfls,
8
@luizfls Tak. Twój kod mówi, że element nie zostanie zmodyfikowany poprzez umieszczenie constodnośnika. Nicol uważa kontener za const, więc autownioskuje o constreferencji. IMO auto const& itemjest łatwiejsze i bardziej przejrzyste. Nie jest jasne, dlaczego std::as_const()tutaj jest dobrze; Widzę, że przydałoby się coś przekazać constkod inny niż ogólny, w którym nie jesteśmy w stanie kontrolować typu, który jest używany, ale z zasięgiem - formożemy, więc wydaje mi się, że jest to dodatkowy szum.
underscore_d
66

Poza tym, co powiedział Nicol Bolas w swojej odpowiedzi , rozważ nowe autosłowo kluczowe:

auto iterator = container.begin();

Dzięki autonie ma sposobu, aby upewnić się, że begin()zwraca stały operator dla niestałego odwołania do kontenera. Więc teraz robisz:

auto const_iterator = container.cbegin();
Stefan Majewsky
źródło
2
@allyourcode: Nie pomaga. Dla kompilatora const_iteratorjest to tylko kolejny identyfikator. Żadna wersja nie używa wyszukiwania typedefs członka decltype(container)::iteratorlub decltype(container)::const_iterator.
aschepler
2
@aschepler Nie rozumiem twojego drugiego zdania, ale myślę, że przegapiłeś „const” przed „auto” w moim pytaniu. Bez względu na to, jakie auto przyjdzie, wydaje się, że const_iterator powinien być const.
allyourcode
26
@allyourcode: To dałoby ci iterator, który jest stały, ale bardzo różni się od iteratora od stałych danych.
aschepler
2
Istnieje prosty sposób na zapewnienie, że masz const_iteratorz auto: Napisz szablon funkcji pomocniczej o nazwie make_constaby zakwalifikować się do argumentu obiektu.
Columbo
17
Może po prostu nie jestem już w umyśle C ++, ale nie widzę związku między pojęciami „prosty sposób” i „napisać szablon funkcji pomocniczej”. ;)
Stefan Majewsky
15

Weź to jako praktyczny przypadek użycia

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

Przypisanie kończy się niepowodzeniem, ponieważ itjest to niekonsekwentny iterator. Jeśli początkowo użyłeś cbegin, iterator miałby odpowiedni typ.

Johannes Schaub - litb
źródło
8

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

dzięki czemu programiści mogą bezpośrednio uzyskać const_iterator z nawet kontenera non-const

Podali ten przykład

vector<MyType> v;

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

Jednak gdy przejście kontenera jest przeznaczone wyłącznie do inspekcji, ogólnie preferowaną praktyką jest użycie const_iterator w celu umożliwienia kompilatorowi diagnozowania naruszeń const

Należy zauważyć, że dokument roboczy wspomina również adapter szablonów, które teraz zostały sfinalizowane jak std::begin()i std::end(), a tym także współpracować z rodzimych tablic. Odpowiednie std::cbegin()i std::cend()dziwnie brakuje na ten czas, ale można je również dodać.

TemplateRex
źródło
5

Natknąłem się na to pytanie ... Wiem, że to już prawie odpowiedź i to tylko boczny węzeł ...

auto const it = container.begin() jest wtedy innym typem auto it = container.cbegin()

różnica dla int[5](używając wskaźnika, który, jak wiem, nie ma metody rozpoczęcia, ale ładnie pokazuje różnicę ... ale działałaby w c ++ 14 dla std::cbegin()i std::cend(), co jest zasadniczo tym, czego należy użyć, gdy jest tutaj) ...

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
Chris G.
źródło
2

iteratori const_iteratormają relację dziedziczenia, a konwersja niejawna następuje w porównaniu z innym typem lub przypisanym do niego.

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

Użycie cbegin()i cend()zwiększy wydajność w tym przypadku.

for (std::vector<T>::const_iterator it=MyVector.cbegin(); it!=MyVector.cend(); ++it)
{
    // ...
}
hkBattousai
źródło
Zajęło mi trochę czasu, aby uświadomić sobie, że oznacza to oszczędność wydajności poprzez unikanie konwersji podczas inicjowania i porównywania iteratorów, a nie popularny mit, że constgłówną zaletą jest wydajność (a nie jest to: semantycznie poprawny i bezpieczny kod). Ale, chociaż masz rację, (A) autosprawia, że ​​nie jest to problem; (B) Mówiąc o wydajności, przeoczyłeś najważniejszą rzecz, którą powinieneś tutaj zrobić: buforuj enditerator, deklarując jego kopię w stanie inicjującym forpętli i porównaj z tym, zamiast otrzymywać nową kopię przez wartość dla każdej iteracji. To poprawi twój punkt widzenia. : P
underscore_d
@underscore_d constmoże zdecydowanie pomóc w osiągnięciu lepszej wydajności, nie z powodu pewnej magii w samym constsłowie kluczowym, ale dlatego, że kompilator może włączyć pewne optymalizacje, jeśli wie, że dane nie zostaną zmodyfikowane, co inaczej nie byłoby możliwe. Sprawdź ten fragment z przemówienia Jasona Turnera, aby zobaczyć na żywo przykład tego.
plan mózgowy
@brainplot Nie powiedziałem, że nie może. Powiedziałem, że to nie jest jego główna korzyść i myślę, że jest zawyżony, gdy prawdziwą korzyścią jest semantycznie poprawny i bezpieczny kod.
underscore_d
@underscore_d Tak, zgadzam się z tym. Właśnie wyjaśniłem, że constmoże (prawie pośrednio) przynieść korzyści w zakresie wydajności; na wypadek, gdyby ktoś czytający ten tekst pomyślał: „Nie zawracam sobie głowy dodawaniem, constjeśli wygenerowany kod nie zostanie w żaden sposób zmieniony”, co nie jest prawdą.
plan mózgowy
0

jego proste, cbegin zwraca stały iterator, gdzie start zwraca tylko iterator

dla lepszego zrozumienia weźmy tutaj dwa scenariusze

scenariusz - 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;
}

uruchomi się, ponieważ tutaj iterator i nie jest stały i można go zwiększyć o 5

teraz użyjmy cbegin i cend oznaczamy je jako scenariusz ciągłych iteracji - 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;
}

to nie zadziała, ponieważ nie można zaktualizować wartości za pomocą cbegin i cend, co zwraca stały iterator

PAVAN KUMAR
źródło