Jaka jest zaleta używania odwołań do przekazywania w pętlach opartych na zakresach?


115

const auto&wystarczy, jeśli chcę wykonywać operacje tylko do odczytu. Jednak wpadłem na

for (auto&& e : v)  // v is non-const

ostatnio kilka razy. To sprawia, że ​​zastanawiam się:

Czy to możliwe, że w niektórych niejasnych przypadkach narożnych korzystanie z odniesień do przekazywania przynosi pewne korzyści w zakresie wydajności w porównaniu z auto&lub const auto&?

( shared_ptrjest podejrzany o niejasne sprawy narożne)


Zaktualizuj Dwa przykłady, które znalazłem w moich ulubionych:

Czy są jakieś wady używania odwołania do const podczas iteracji po typach podstawowych?
Czy mogę łatwo iterować wartości mapy za pomocą pętli for opartej na zakresie?

Skoncentruj się na pytaniu: dlaczego miałbym chcieć używać auto && w oparciu o zakres dla pętli?


4
Czy naprawdę widzisz to „często”?
Wyścigi lekkości na orbicie

2
Nie jestem pewien, czy w Twoim pytaniu jest wystarczająco dużo kontekstu, abym mógł ocenić, jak „szalone” jest to, gdzie je widzisz.
Wyścigi lekkości na orbicie

2
@LightnessRacesinOrbit Krótko mówiąc: dlaczego miałbym chcieć używać auto&&pętli opartych na zasięgu?
Ali

Odpowiedzi:


94

Jedyną zaletą, jaką widzę, jest to, że iterator sekwencji zwraca referencję proxy i musisz operować na tym odwołaniu w sposób inny niż stały. Weźmy na przykład pod uwagę:

#include <vector>

int main()
{
    std::vector<bool> v(10);
    for (auto& e : v)
        e = true;
}

To się nie kompiluje, ponieważ rvalue vector<bool>::referencezwrócony z iteratornie będzie wiązał się z odwołaniem do wartości l-wartości innej niż stała. Ale to zadziała:

#include <vector>

int main()
{
    std::vector<bool> v(10);
    for (auto&& e : v)
        e = true;
}

Biorąc to wszystko pod uwagę, nie kodowałbym w ten sposób, gdybyś nie wiedział, że musisz spełnić taki przypadek użycia. Tzn. Nie zrobiłbym tego bezinteresownie, ponieważ powoduje to , że ludzie zastanawiają się, co zamierzasz. A gdybym to zrobił, nie zaszkodziłoby dodać komentarz wyjaśniający, dlaczego:

#include <vector>

int main()
{
    std::vector<bool> v(10);
    // using auto&& so that I can handle the rvalue reference
    //   returned for the vector<bool> case
    for (auto&& e : v)
        e = true;
}

Edytować

Ten mój ostatni przypadek naprawdę powinien być szablonem, który ma sens. Jeśli wiesz, że pętla zawsze obsługuje odwołanie do serwera proxy, autodziałałaby równie dobrze auto&&. Ale kiedy pętla czasami obsługiwała odwołania niebędące proxy, a czasami odwołania proxy, myślę, że auto&&byłoby to preferowane rozwiązanie.


3
Z drugiej strony nie ma wyraźnej wady , prawda? (Oprócz potencjalnie dezorientujących ludzi, o których nie sądzę, aby były warte wzmianki osobiście).
ildjarn

7
Innym terminem na pisanie kodu, który niepotrzebnie dezorientuje ludzi, jest: pisanie błędnego kodu. Najlepiej, aby kod był tak prosty, jak to tylko możliwe, ale nie prostszy. Pomoże to w odliczaniu błędów. Biorąc to pod uwagę, w miarę jak && staje się bardziej znane, to może za 5 lat ludzie zaczną oczekiwać auto && idiomu (zakładając, że w rzeczywistości nie zaszkodzi). Nie wiem, czy to się stanie, czy nie. Ale prostota jest dla patrzącego, a jeśli piszesz nie tylko dla siebie, weź pod uwagę swoich czytelników.
Howard Hinnant

7
Wolę, const auto&gdy chcę, aby kompilator pomógł mi sprawdzić, czy przypadkowo nie modyfikuję elementów w sekwencji.
Howard Hinnant

31
Osobiście lubię używać auto&&w kodzie ogólnym, w którym muszę zmodyfikować elementy sekwencji. Jeśli tego nie zrobię, po prostu będę się tego trzymać auto const&.
Xeo

7
@Xeo: +1 To dzięki takim entuzjastom jak ty, nieustannie eksperymentującym i dążącym do lepszych sposobów robienia rzeczy, C ++ wciąż ewoluuje. Dziękuję Ci. :-)
Howard Hinnant

26

Korzystanie auto&&lub odniesienia uniwersalne z zakresu oparte for-loop ma tę zaletę, że oddaje to, co dostajesz. W przypadku większości typów iteratorów prawdopodobnie otrzymasz a T&lub a T const&dla jakiegoś typu T. Ciekawym przypadkiem jest sytuacja, w której dereferencja iteratora daje tymczasowe: C ++ 2011 ma łagodne wymagania, a iteratory niekoniecznie są wymagane do uzyskania lwartości. Użycie odwołań uniwersalnych odpowiada przekazywaniu argumentów w std::for_each():

template <typename InIt, typename F>
F std::for_each(InIt it, InIt end, F f) {
    for (; it != end; ++it) {
        f(*it); // <---------------------- here
    }
    return f;
}

Przedmiotem funkcji fmożna leczyć T&, T const&lub Tw inny sposób. Dlaczego fortreść pętli opartej na zakresie miałaby być inna? Oczywiście, aby faktycznie skorzystać z wywnioskowania typu przy użyciu uniwersalnych odwołań, musisz je odpowiednio przekazać:

for (auto&& x: range) {
    f(std::forward<decltype(x)>(x));
}

Oczywiście użycie std::forward()oznacza, że ​​akceptujesz wszystkie zwrócone wartości, z których chcesz przenieść. Czy takie obiekty mają sens w kodzie innym niż szablon, którego nie wiem (jeszcze?). Mogę sobie wyobrazić, że użycie uniwersalnych odwołań może dostarczyć kompilatorowi więcej informacji, aby wykonać właściwą rzecz. W kodzie opartym na szablonach nie podejmuje żadnych decyzji dotyczących tego, co powinno się stać z obiektami.


9

Praktycznie zawsze używam auto&&. Po co dać się ugryźć przez skrajny przypadek, kiedy nie musisz? Jest też krótszy do pisania i po prostu uważam, że jest bardziej ... przejrzysty. Kiedy używasz auto&& x, wiesz, że xtak jest za *itkażdym razem.


29
Mój problem polega na tym, że constpoddajesz się, auto&&jeśli const auto&wystarczy. Pytanie dotyczy narożnych przypadków, w których mogę zostać ugryziony. Jakie są przypadki narożne, o których jeszcze nie wspomniał Dietmar ani Howard?
Ali

2
Oto sposób na gryzą jeśli zrobić korzystania auto&&. Jeśli typ, który przechwytujesz, powinien zostać przeniesiony w treści pętli (na przykład) i zostanie zmieniony na późniejszy, tak aby przekształcił się w const&typ, twój kod będzie po cichu nadal działać, ale twoje ruchy będą kopiami. Ten kod będzie bardzo zwodniczy. Jeśli jednak wyraźnie określisz typ jako odniesienie do wartości r, ktokolwiek zmieni typ kontenera, otrzyma błąd kompilacji, ponieważ naprawdę chciałeś, aby te obiekty zostały przeniesione, a nie skopiowane ...
cyberbisson

@cyberbisson Właśnie natknąłem się na to: jak mogę wymusić odwołanie do wartości r bez jawnego określenia typu? Coś jak for (std::decay_t<decltype(*v.begin())>&& e: v)? Myślę, że jest lepszy sposób ...
Jerry Ma,

@JerryMa To zależy od tego, co wiesz o typie. Jak wspomniano powyżej, auto&&da "uniwersalne odniesienie", więc cokolwiek innego niż to da ci bardziej szczegółowy typ. Jeśli vprzyjmuje się, że jest a vector, możesz to zrobić decltype(v)::value_type&&, co, jak zakładam, chciałeś, biorąc wynik z operator*typu iteratora. Możesz również decltype(begin(v))::value_type&&sprawdzić typy iteratora zamiast kontenera. Jeśli jednak mamy tak mało wglądu w typ, możemy uznać, że trochę jaśniej byłoby po prostu pójść za auto&&...
cyberbisson
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.