2012-10-29 3 views
77

const auto& sarebbe sufficiente se si desidera eseguire operazioni di sola lettura. Tuttavia, mi sono imbattuto inQual è il vantaggio dell'utilizzo di riferimenti universali in loop basati su intervalli?

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

un paio di volte di recente. Questo mi fa meravigliare:

E 'possibile che in alcuni casi d'angolo oscuri ci sia qualche vantaggio in termini di prestazioni nell'uso di riferimenti universali, rispetto a auto& o const auto&?

(shared_ptr è un sospetto per i casi angolo oscuro)


Aggiornamento Due esempi che ho trovato nei miei preferiti:

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?

Si prega di concentrarsi sul domanda: perché dovrei voler usare auto & & in loop per loop?

+3

Lo vedi veramente "spesso"? –

+2

Non sono sicuro che nella tua domanda ci sia abbastanza contesto per capire quanto "pazzo" sia dove lo stai vedendo. –

+2

@LightnessRacesinOrbit Per farla breve: perché dovrei usare 'auto &&' in loop-based per cicli? – Ali

risposta

64

L'unico vantaggio che posso vedere è quando l'iteratore di sequenza restituisce un riferimento proxy e occorre operare su tale riferimento in modo non costante. Per esempio considerare:

#include <vector> 

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

Questo non viene compilato perché rvalue vector<bool>::reference restituito dal iterator non legarsi a un riferimento lvalue non-const. Ma questo funzionerà:

#include <vector> 

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

Detto questo, non vorrei codice in questo modo a meno che non si sapeva avevi bisogno di soddisfare un simile caso d'uso. Cioè Non lo farei gratuitamente perché lo fa perché le persone si chiedono cosa stai facendo. E se l'ho fatto, non sarebbe male per includere un commento sul perché:

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

Modifica

Quest'ultimo caso mio in realtà dovrebbe essere un modello per dare un senso. Se sai che il ciclo gestisce sempre un riferimento proxy, allora auto funzionerebbe come auto&&. Ma quando il ciclo a volte gestiva riferimenti non proxy e talvolta riferimenti proxy, allora penso che auto&& diventerebbe la soluzione preferita.

+2

D'altra parte, non esiste un _disadvantage_ esplicito, c'è? (A parte le persone potenzialmente confuse, che non penso valga la pena di menzionare personalmente). – ildjarn

+0

Questo è un punto interessante. Presumibilmente non c'è modo di incoraggiare il range-'for' a preferire 'T :: const_iterator'? Credo che 'begin()' e 'end()' siano usati. Sembra un po 'di supervisione, tranne per il fatto che è possibile utilizzare i valori di riferimento. Quindi sì, forse questa è la risposta. –

+3

Un altro termine per scrivere codice che confonde inutilmente le persone è: scrivere codice confuso. È meglio rendere il tuo codice il più semplice possibile, ma non più semplice. Ciò contribuirà a mantenere basso il bug. Detto questo, come && diventa più familiare, quindi forse tra 5 anni le persone si aspetteranno un auto && idiom (supponendo che in realtà non fa male). Non so se succederà o no. Ma semplice è negli occhi di chi guarda, e se stai scrivendo per qualcosa di più di te stesso, prendi in considerazione i tuoi lettori. –

19

Utilizzare auto&& o universal references con un intervallo basato su for -loop ha il vantaggio di acquisire ciò che si ottiene. Per la maggior parte dei tipi di iteratori probabilmente avrai uno T& o un T const& per un certo tipo T. Il caso interessante è dove il dereferenziamento di un iteratore produce un temporaneo: C++ 2011 ha requisiti rilassati e gli iteratori non sono necessariamente tenuti a fornire un lvalue.L'uso di riferimenti universali corrisponde l'inoltro argomento 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; 
} 

L'oggetto f funzione può trattare T&, T const& e T in modo diverso. Perché il corpo di un intervallo for basato su intervallo deve essere diverso? Naturalmente, a prendere realmente vantaggio di aver dedotto dal tipo utilizzando i riferimenti universali avresti bisogno di trasmetterli corrispondentemente:

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

Naturalmente, utilizzando std::forward() significa che si accetta tutti i valori restituiti da spostare da. Se oggetti come questo hanno molto senso nel codice non-template che non conosco (ancora?). Posso immaginare che l'uso di riferimenti universali possa offrire più informazioni al compilatore per fare la cosa giusta. Nel codice dei modelli rimane fuori da qualsiasi decisione su cosa dovrebbe accadere con gli oggetti.

5

Uso praticamente sempre auto&&. Perché essere morso da un caso limite quando non è necessario? È più breve da digitare anche, e semplicemente lo trovo più ... trasparente. Quando usi auto&& x, allora sai che x è esattamente *it, ogni volta.

+20

Il mio problema è che stai abbandonando 'const'-ness con' auto && 'se' const auto & 'è sufficiente. La domanda richiede i casi d'angolo in cui posso essere morso. Quali sono i casi d'angolo che non sono ancora stati menzionati da Dietmar o Howard? – Ali