2012-10-29 10 views
77

const auto& byłoby wystarczające, jeśli chcę wykonywać operacje tylko do odczytu. Jednak ostatnio kilka razy zdarzyło mi się natrafić naJaka jest zaleta korzystania z uniwersalnych referencji w pętlach opartych na zasięgu?

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

. Zastanawiam się:

Czy jest możliwe, że w niektórych niejasnych przypadkach narożnych występuje pewna korzyść z wydajności przy użyciu odniesień uniwersalnych, w porównaniu do auto& lub const auto&?

(shared_ptr jest podejrzany o niejasnych przypadkach narożnych)


Aktualizacja dwa przykłady, które znalazłem w moich ulubionych:

Any disadvantage of using const reference when iterating over basic types?
Can I easily iterate over the values of a map using a range-based for loop?

Należy skoncentrować się na pytanie: dlaczego chciałbym użyć auto & & w zakresie dla pętli?

+3

Czy widzisz to "często"? –

+2

Nie jestem pewien, czy w twoim pytaniu jest wystarczająco dużo kontekstu, aby ocenić, jak "szalone" jest to, co widzisz. –

+2

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

Odpowiedz

64

Jedyną zaletą jaką widzę jest to, że iterator sekwencji zwraca referencję proxy i trzeba działać na tym odwołaniu w sposób inny niż const. Na przykład rozważyć:

#include <vector> 

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

nie skompilować, ponieważ RValue vector<bool>::reference wrócił z iterator nie będzie wiązać się z const odniesienia lwartości. Ale to zadziała:

#include <vector> 

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

Nie zakodowałem w ten sposób, chyba że wiedziałeś, że musisz zaspokoić taki przypadek. To znaczy. Nie zrobiłbym tego bezinteresownie, ponieważ powoduje, że ludzie zastanawiają się, co robisz. A gdybym zrobił to zrobić, to nie zaszkodzi to komentarz, 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; 
} 

Edycja

Ten ostatni przypadek kopalni powinien być naprawdę szablon sensu. Jeśli wiesz, że pętla zawsze obsługuje odwołanie do proxy, wówczas auto zadziała równie dobrze jak auto&&. Ale kiedy pętla czasami obsługiwała odniesienia nie-proxy, a czasem referencje proxy, wtedy myślę, że rozwiązaniem byłoby rozwiązanie auto&&.

+2

Z drugiej strony nie ma wyraźnej _disadvantage_, prawda? (Oprócz potencjalnie zagmatwanych osób, o których nie sądzę, warto wspomnieć, osobiście.) – ildjarn

+0

To interesujący punkt. Przypuszczalnie nie ma sposobu na zachęcanie zasięgu - 'for' do preferowania' T :: const_iterator'? Wierzę, że używane są 'begin()' i 'end()'. Wygląda na to, że trochę przeoczysz, z wyjątkiem tego, że możesz użyć refunów rvalue. Więc może to jest odpowiedź. –

+3

Innym terminem pisania kodu niepotrzebnie dezorientującego ludzi jest: pisanie skonfundowanego kodu. Najlepiej uczynić swój kod tak prostym, jak to tylko możliwe, ale nie prostszym. Pomoże to w odliczaniu błędów. To powiedziawszy, ponieważ && staje się bardziej znajomy, to może za 5 lat ludzie będą oczekiwać znaku auto && idiom (zakładając, że tak naprawdę nic nie szkodzi). Nie wiem, czy to się wydarzy, czy nie. Ale proste jest w oczach patrzącego, a jeśli piszesz na więcej niż tylko siebie, weź swoich czytelników pod uwagę. –

19

Korzystanie z lub universal references z opcją opartą na odległościach for ma tę zaletę, że rejestruje to, co dostajesz. Dla większości rodzajów iteratorów prawdopodobnie uzyskasz albo T& albo T const& dla pewnego typu T. Ciekawym przypadkiem jest to, że dereferencja z iteratorem jest tymczasowa: C++ 2011 uzyskał luźniejsze wymagania, a iteratory niekoniecznie muszą dostarczyć wartość l.Zastosowanie uniwersalnych odniesień mecze przekazywanie argument 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; 
} 

Funkcja obiektu f można traktować T&, T const& i T inaczej. Dlaczego ciało oparte na pasmach for może być inne? Oczywiście, aby rzeczywiście skorzystać z posiadania wywnioskować typ używając uniwersalnych odniesień którą musisz przekazać je odpowiednio:

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

oczywiście używając std::forward() oznacza akceptację wszelkich zwracanych wartości zostać przeniesiony z. Czy takie obiekty mają sens w kodzie nie szablonowym, którego nie znam (jeszcze?). Mogę sobie wyobrazić, że używanie uniwersalnych odniesień może dostarczyć kompilatorowi więcej informacji, aby zrobić Właściwe Prawie. W szablonowym kodzie nie podejmuje żadnej decyzji o tym, co powinno się stać z przedmiotami.

5

W praktyce zawsze używam auto&&. Po co cię ugryźć, kiedy nie musisz? Jest również krótszy do wpisania i po prostu uważam, że jest bardziej ... przezroczysty. Kiedy używasz auto&& x, to wiesz, że x jest dokładnie *it, za każdym razem.

+20

Moim problemem jest to, że rezygnujesz z 'const'-ness z' auto && 'jeśli' const auto & 'wystarcza. Pytanie dotyczy narożnych przypadków, w których mogę zostać ugryziony. Jakie są sprawy narożne, które nie zostały jeszcze wspomniane przez Dietmara lub Howarda? – Ali