La nuova parola chiave “auto”; Quando dovrebbe essere usato per dichiarare un tipo di variabile?

Possibile duplicato:
Quanto costa troppo con la parola chiave auto C ++ 0x

Abbiamo (come comunità) avuto abbastanza esperienza per determinare quando e / o se l’auto viene abusata?

Quello che sto cercando è una guida alle migliori pratiche

  • quando usare l’auto
  • quando dovrebbe essere evitato

Semplici regole empiriche che possono essere rapidamente seguite nell’80% dei casi.

Come contesto, questa domanda è scaturita dalla mia risposta qui

Penso che quando il tipo è molto conosciuto tra i co-programmatori che lavorano (o lavorerebbero) nel tuo progetto, allora può essere usato auto , come nel seguente codice:

 //good : auto increases readability here for(auto it = v.begin(); it != v.end(); ++it) //v is some [std] container { //.. } 

O, più in generale,

 //good : auto increases readability here for(auto it = std::begin(v); it != std::end(v); ++it)//v could be array as well { //.. } 

Ma quando il tipo non è molto conosciuto e usato raramente, allora penso che l’ auto sembra ridurre la leggibilità, come qui:

 //bad : auto decreases readability here auto obj = ProcessData(someVariables); 

Mentre nel primo caso, l’uso di auto sembra molto buono e non riduce la leggibilità e, pertanto, può essere ampiamente utilizzato, ma nel secondo caso riduce la leggibilità e quindi non dovrebbe essere utilizzato.


Un altro punto in cui è ansible utilizzare l’ auto è quando si utilizzano le new funzioni 1 o make_* , ad esempio qui:

 //without auto. Not that good, looks cumbersome SomeType::SomeOtherType * obj1 = new SomeType::SomeOtherType(); std::shared_ptr obj2 = std::make_shared(args...); std::unique_ptr obj2 = std::make_unique(args...); //With auto. good : auto increases readability here auto obj1 = new SomeType::SomeOtherType(); auto obj2 = std::make_shared(args...); auto obj3 = std::make_unique(args...); 

Qui è molto buono, in quanto riduce l’uso della tastiera, senza ridurre la leggibilità, in quanto chiunque può conoscere il tipo di oggetti creati, semplicemente guardando il codice.

1. Evita comunque di usare new e semplici puntatori.


A volte, il tipo è così irrilevante che la conoscenza del tipo non è nemmeno necessaria, come nel modello di espressione ; infatti, praticamente è imansible scrivere il tipo (correttamente), in questi casi l’ auto è un sollievo per i programmatori. Ho scritto una libreria di modelli di espressioni che può essere utilizzata come:

 foam::composition::expression x; auto s = x * x; //square auto c = x * x * x; //cube for(int i = 0; i < 5 ; i++ ) std::cout << s(i) << ", " << c(i) << std::endl; 

Produzione:

 0, 0 1, 1 4, 8 9, 27 16, 64 

Ora confronta il codice sopra con il seguente codice equivalente che non usa auto :

 foam::composition::expression x; //scroll horizontally to see the complete type!! foam::composition::expression, foam::composition::expression, foam::operators::multiply>> s = x * x; //square foam::composition::expression, foam::composition::expression, foam::operators::multiply> >, foam::composition::expression, foam::operators::multiply>> c = x * x * x; //cube for(int i = 0; i < 5 ; i++ ) std::cout << s(i) << ", " << c(i) << std::endl; 

Come puoi vedere, in questi casi l' auto rende la tua vita esponenzialmente più facile. Le espressioni usate sopra sono molto semplici; pensa al tipo di espressioni più complesse:

 auto a = x * x - 4 * x + 4; auto b = x * (x + 10) / ( x * x+ 12 ); auto c = (x ^ 4 + x ^ 3 + x ^ 2 + x + 100 ) / ( x ^ 2 + 10 ); 

Il tipo di tali espressioni sarebbe ancora più grande e brutto, ma grazie all'automatico, ora possiamo consentire al compilatore di inferire il tipo di espressioni.


Quindi la linea di fondo è: la parola chiave auto potrebbe aumentare o diminuire la chiarezza e la leggibilità del tuo codice, a seconda del contesto . Se il contesto chiarisce di che tipo si tratta, o almeno come dovrebbe essere usato (nel caso di un iteratore contenitore standard) o se la conoscenza del tipo effettivo non è nemmeno necessaria (come nei modelli di espressione), allora auto dovrebbe essere usato , e se il contesto non lo rende chiaro e non è molto comune (come il secondo caso sopra), allora dovrebbe essere meglio evitato .

Facile. Usalo quando non ti importa di che tipo si tratta. Per esempio

 for (auto i : some_container) { ... 

Tutto quello che mi interessa qui è che i sono qualunque cosa sia nel contenitore.

È un po ‘come typedef.

 typedef float Height; typedef double Weight; //.... Height h; Weight w; 

Qui, non mi interessa se h e w sono float o double, solo che sono di qualsiasi tipo sia adatto per esprimere altezze e pesi .

O prendere in considerazione

 for (auto i = some_container .begin (); ... 

Qui tutto quello che mi interessa è che è un iteratore adatto, che supporta l’ operator++() , è una specie di digitazione anatra a questo riguardo.

Anche il tipo di lambda non può essere digitato, quindi auto f = []... è buono stile. L’alternativa è lanciare su std::function ma viene fornito con overhead.

Non riesco davvero a concepire un “abuso” di auto . Il più vicino che posso immaginare è privarsi di una conversione esplicita ad un tipo significativo, ma non si userebbe l’ auto per quello, si costruisce un object del tipo desiderato.

Se è ansible rimuovere alcune ridondanze nel codice senza introdurre effetti collaterali, è necessario farlo.

Applicherei la stessa regola di var in C #: usala liberamente . Aumenta la leggibilità. A meno che il tipo di una variabile sia effettivamente abbastanza importante da essere dichiarato esplicitamente, in quali casi questo dovrebbe essere fatto (duh).

Tuttavia, ritengo che (specialmente nei linguaggi tipizzati staticamente) il compilatore sia molto più bravo a tracciare i tipi di noi rispetto a noi. La maggior parte delle volte, il tipo esatto non è comunque molto importante (altrimenti le interfacce non funzionerebbero nella pratica). È più importante essere consapevoli di quali operazioni sono consentite. Il contesto dovrebbe dircelo.

Inoltre, auto può effettivamente prevenire bug , impedendo conversioni implicite indesiderate in initialisations. Generalmente, la dichiarazione Foo x = y; eseguirà una conversione implicita se y non è di tipo Foo e esiste una conversione implicita. Questo è il motivo per evitare di avere conversioni implicite in primo luogo. Sfortunatamente, il C ++ ne ha già troppi.

Scrittura auto x = y; preverrà questo problema in linea di principio .

D’altra parte, dovrebbe essere chiaro che quando sto eseguendo calcoli che assumono questo o quel numero di byte in un numero intero, il tipo esplicito della variabile deve essere noto e dovrebbe essere chiaramente indicato.

Non tutti i casi sono chiari, ma ritengo che la maggior parte lo siano, e questo

  1. nella maggior parte dei casi è facile vedere se è necessario conoscere un tipo esplicito e
  2. la necessità di tipi espliciti è relativamente rara.

Eric Lippert , lo sviluppatore principale del team di compilatori C #, ha dichiarato lo stesso per quanto riguarda var .

Penso che la risposta alla tua prima domanda sia una specie di no. Sappiamo abbastanza da mettere insieme alcune linee guida su quando utilizzare o evitare l’ auto , ma lasciano ancora alcuni casi in cui il meglio che possiamo attualmente dire è che non possiamo ancora dare molto in termini di consigli obiettivi su di loro.

Il caso ovvio in cui devi quasi usarlo è in un modello quando vuoi (ad esempio) il tipo corretto per contenere il risultato di qualche operazione su due parametri generici. In un caso come questo, l’unica possibilità di abuso non sarebbe in realtà un abuso della stessa auto , ma se il tipo generale di operazione che stai facendo (o il tipo di modello che stai scrivendo, ecc.) È qualcosa che stare meglio evitando.

Ci sono anche almeno alcune situazioni in cui hai chiaramente bisogno di evitare l’ auto . Se stai usando qualcosa come un tipo di proxy in cui sei dipendente dalla conversione da proxy-> target per fare parte del lavoro in questione, auto will (tenta di) creare un target dello stesso tipo dell’origine in modo che la conversione non avverrà. In alcuni casi, ciò può solo ritardare la conversione, ma in altri non funzionerà affatto (ad esempio, se il tipo di proxy non supporta l’assegnazione, che è spesso il caso).

Un altro esempio potrebbe essere quando è necessario assicurare che una particolare variabile abbia un tipo specifico per il gusto di qualcosa come un’interfaccia esterna. Per esempio, considera l’applicazione della maschera di rete a un indirizzo IP (v4). Per ragioni, supponiamo che tu stia lavorando con i singoli ottetti dell’indirizzo (ad esempio, rappresentando ciascuno come un unsigned char ), quindi finiamo con qualcosa come octets[0] & mask[0] . Grazie alle regole di promozione del tipo di C, anche se entrambi gli operandi sono di tipo unsigned char , il risultato sarà in genere int . Tuttavia, abbiamo bisogno che il risultato sia un unsigned char (ad esempio, un ottetto) e non un int (tipicamente 4 ottetti). Come tale, in questa situazione, l’ auto sarebbe quasi certamente inappropriata.

Ciò lascia comunque molti casi in cui è comunque una sentenza. La mia tendenza per questi casi è quella di considerare auto come predefinita, e usare solo un tipo esplicito nei casi che sono almeno un po ‘come il caso precedente che ho citato sopra – anche se un particolare tipo non è necessario per il corretto operazione che voglio davvero un tipo particolare, anche se ciò potrebbe implicare una conversione implicita.

La mia ipotesi (ma è solo un’ipotesi) è che col tempo probabilmente tenderò ancora di più in quella direzione. Man mano che mi abituo di più al compilatore, individuando i tipi, trovo che un buon numero di casi in cui attualmente penso che dovrei specificare il tipo, in realtà non ne ho bisogno e il codice andrà bene.

Sospetto che molti di noi (e il più vecchio / più esperto di noi, probabilmente il peggio che ne saremo) utilizzeranno tipi espliciti per motivi che alla fine riconducono a qualche sentimento sulle prestazioni e credendo che la nostra scelta migliorerà le prestazioni . In parte, forse, abbiamo ragione – ma come molti di noi hanno trovato, le nostre ipotesi sono spesso sbagliate (specialmente quando si basano su ipotesi implicite), e compilatori e processori generalmente migliorano tali cose nel tempo pure.

Ho usato le lingue con inferenza di tipo completo. Non vedo alcun motivo per non mettere l’ auto ovunque sia tecnicamente ansible *. Infatti potrei aver già scritto auto i = 0; , dove int è un carattere più corto di auto . Non sono nemmeno sicuro di averlo fatto perché il fondo è: non mi interessa la digitazione evidente.

*: per esempio auto int[] = { 0, 1, 2, 3 } non funziona.

Utilizzalo solo con tipi ripetitivi lunghi come i modelli lunghi e i tipi di funzione lambda. Cerca di evitarlo se puoi chiarire le cose.