Odpowiedzi:
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 SomeFunctor
było swobodnie modyfikować otrzymany parametr. Oczywiście, SomeFunctor
moż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 SomeFunctor
nie mogą modyfikować elementów wektora (oczywiście bez ograniczenia). Dostajemy jawnie const_iterator
s, 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.
std::cbegin/cend
bezpłatnych funkcji, które std::begin/std::end
istnieją. Był to nadzór komitetu. Gdyby te funkcje istniały, byłby to na ogół sposób ich użycia.
std::cbegin/cend
zostanie dodany w C ++ 14. Zobacz en.cppreference.com/w/cpp/iterator/begin
for(auto &item : std::as_const(vec))
równoważne z for(const auto &item : vec)
?
const
odnośnika. Nicol uważa kontener za const, więc auto
wnioskuje o const
referencji. IMO auto const& item
jest łatwiejsze i bardziej przejrzyste. Nie jest jasne, dlaczego std::as_const()
tutaj jest dobrze; Widzę, że przydałoby się coś przekazać const
kod inny niż ogólny, w którym nie jesteśmy w stanie kontrolować typu, który jest używany, ale z zasięgiem - for
możemy, więc wydaje mi się, że jest to dodatkowy szum.
Poza tym, co powiedział Nicol Bolas w swojej odpowiedzi , rozważ nowe auto
słowo kluczowe:
auto iterator = container.begin();
Dzięki auto
nie 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();
const_iterator
jest to tylko kolejny identyfikator. Żadna wersja nie używa wyszukiwania typedefs członka decltype(container)::iterator
lub decltype(container)::const_iterator
.
const_iterator
z auto
: Napisz szablon funkcji pomocniczej o nazwie make_const
aby zakwalifikować się do argumentu obiektu.
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ż it
jest to niekonsekwentny iterator. Jeśli początkowo użyłeś cbegin, iterator miałby odpowiedni typ.
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ć.
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
iterator
i const_iterator
mają 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)
{
// ...
}
const
główną zaletą jest wydajność (a nie jest to: semantycznie poprawny i bezpieczny kod). Ale, chociaż masz rację, (A) auto
sprawia, że nie jest to problem; (B) Mówiąc o wydajności, przeoczyłeś najważniejszą rzecz, którą powinieneś tutaj zrobić: buforuj end
iterator, deklarując jego kopię w stanie inicjującym for
pętli i porównaj z tym, zamiast otrzymywać nową kopię przez wartość dla każdej iteracji. To poprawi twój punkt widzenia. : P
const
może zdecydowanie pomóc w osiągnięciu lepszej wydajności, nie z powodu pewnej magii w samym const
sł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.
const
moż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, const
jeśli wygenerowany kod nie zostanie w żaden sposób zmieniony”, co nie jest prawdą.
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