Pętla oparta na zakresie C ++ 11: pobierz element według wartości lub odwołania do const

215

Czytając niektóre przykłady pętli opartych na zakresie, sugerują dwa główne sposoby 1 , 2 , 3 , 4

std::vector<MyClass> vec;

for (auto &x : vec)
{
  // x is a reference to an item of vec
  // We can change vec's items by changing x 
}

lub

for (auto x : vec)
{
  // Value of x is copied from an item of vec
  // We can not change vec's items by changing x
}

Dobrze.

Kiedy nie potrzebujemy zmieniać vecelementów, IMO, Przykłady sugerują użycie drugiej wersji (według wartości). Dlaczego nie sugerują czegoś, do czego się constodwołują (Przynajmniej nie znalazłem żadnej bezpośredniej sugestii):

for (auto const &x : vec) // <-- see const keyword
{
  // x is a reference to an const item of vec
  // We can not change vec's items by changing x 
}

Czy to nie jest lepsze? Czy nie unika się zbędnej kopii w każdej iteracji, gdy jest to const?

masoud
źródło

Odpowiedzi:

390

Jeśli nie chcesz zmieniać elementów, a także chcesz unikać wykonywania kopii, auto const &to właściwy wybór:

for (auto const &x : vec)

Ktokolwiek zaproponuje ci użycie, auto &jest zły. Ignoruj ​​ich.

Oto podsumowanie:

  • Wybierz, auto xkiedy chcesz pracować z kopiami.
  • Wybierz, auto &xkiedy chcesz pracować z oryginalnymi elementami i możesz je modyfikować.
  • Wybierz, auto const &xkiedy chcesz pracować z oryginalnymi elementami i nie będziesz ich modyfikować.
Nawaz
źródło
21
Dzięki za świetną odpowiedź. Myślę, że należy również zauważyć, że const auto &xjest to odpowiednik twojego trzeciego wyboru.
smg
7
@mloskot: To jest równoważne. (a co rozumiesz przez „ale to ta sama składnia” ? Składnia jest zauważalnie inna.)
Nawaz
14
Brak: auto&&gdy nie chcesz tworzyć niepotrzebnej kopii i nie przejmuj się, czy ją zmodyfikujesz, czy nie, a chcesz po prostu pracować.
Jak - Adam Nevraumont
4
Czy rozróżnienie odniesienia ma zastosowanie do kopii, jeśli pracujesz tylko z podstawowymi typami, takimi jak int / double?
4
@racarate: Nie mogę komentować ogólnej prędkości i myślę, że nikt nie może, bez uprzedniego jej profilowania. Szczerze mówiąc, nie będę opierać wyboru na szybkości, a nie na przejrzystości kodu. Jeśli chcę niezmienność, constz pewnością skorzystam. Jednak czy będzie auto const &, czy auto const ma niewielką różnicę. Wolałbym auto const &być bardziej konsekwentny.
Nawaz
23

Jeśli masz std::vector<int>lub std::vector<double>, możesz użyć auto(z kopiowaniem wartości) zamiast const auto&, ponieważ kopiowanie intlub doublejest tanie:

for (auto x : vec)
    ....

Ale jeśli masz std::vector<MyClass>, gdzie MyClassjest trochę nietrywialna semantyka kopiowania (np. std::stringNiektóre złożone klasy niestandardowe itp.), To sugeruję użycie w const auto&celu uniknięcia głębokich kopii :

for (const auto & x : vec)
    ....
Mr.C64
źródło
3

Kiedy nie potrzebujemy zmieniać vecelementów, Przykłady sugerują użycie pierwszej wersji.

Następnie podają niewłaściwą sugestię.

Dlaczego nie sugerują czegoś, co stanowi stałe odniesienia

Ponieważ podają niewłaściwą sugestię :-) To, o czym wspominasz, jest poprawne. Jeśli chcesz tylko obserwować obiekt, nie ma potrzeby tworzenia kopii i nie ma potrzeby constodwoływania się do niego.

EDYTOWAĆ:

Widzę, że wszystkie odnośniki zawierają przykłady iteracji w zakresie intwartości lub innego podstawowego typu danych. W takim przypadku, ponieważ kopiowanie intnie jest drogie, tworzenie kopii jest zasadniczo równoważne (jeśli nie bardziej wydajne niż) obserwowaniu const &.

Nie dotyczy to jednak ogólnie typów zdefiniowanych przez użytkownika. Kopiowanie UDT może być kosztowne, a jeśli nie masz powodu do tworzenia kopii (np. Modyfikowania odzyskanego obiektu bez zmiany oryginalnego), najlepiej jest użyć const &.

Andy Prowl
źródło
1

Będę tu przeciwny i powiem, że nie ma potrzeby auto const &w zakresie opartym na pętli. Powiedz mi, jeśli uważasz, że poniższa funkcja jest głupia (nie w swoim celu, ale w sposobie, w jaki jest napisana):

long long SafePop(std::vector<uint32_t>& v)
{
    auto const& cv = v;
    long long n = -1;
    if (!cv.empty())
    {
        n = cv.back();
        v.pop_back();
    }
    return n;
}

Tutaj autor stworzył stałe odwołanie do vużycia dla wszystkich operacji, które nie modyfikują v. To jest, moim zdaniem, głupie, a ten sam argument można zastosować auto const &jako zmienną w zakresie opartym na pętli zamiast po prostu auto &.

Benjamin Lindley
źródło
3
@BenjaminLindley: Mogę więc wywnioskować, że również się kłócisz const_iteratorw pętli for? W jaki sposób upewniasz się, że nie zmieniasz oryginalnych przedmiotów z pojemnika podczas iteracji?
Nawaz
2
@BenjaminLindley: Dlaczego twój kod nie skompilowałby się bez const_iterator? Niech zgadnę, pojemnik, nad którym iterujesz, jest const. Ale od czego to constsię zaczyna? Gdzieś używasz, constaby się upewnić?
Nawaz
8
Zaletą tworzenia obiektów constsą zalety używania private/ protectedw klasach. Pozwala to uniknąć dalszych błędów.
masoud
2
@BenjaminLindley: Oh, przeoczyłem to. W takim przypadku implementacja jest również głupia. Ale jeśli ta funkcja się nie zmieni v, wdrożenie będzie w porządku IMO. A jeśli pętla nigdy nie modyfikuje obiektów, przez które się iteruje, wydaje mi się słuszne, aby mieć referencję const. Rozumiem jednak twój punkt widzenia. Mój polega na tym, że pętla reprezentuje jednostkę kodu, podobnie jak funkcja, i wewnątrz tej jednostki nie ma instrukcji, która musiałaby modyfikować wartość. Dlatego można „oznaczyć” całą jednostkę jako const, tak jak w przypadku constfunkcji członka.
Andy Prowl
1
Więc uważasz, że const &oparte na zakresie jest głupie, ponieważ napisałeś nieistotny wyimaginowany przykład wyimaginowanego programisty, który zrobił coś głupiego z kwalifikacją cv ? ... OK więc
underscore_d