Quanto costa troppo con la parola chiave automatica C ++ 11?


218

Sto usando la nuova autoparola chiave disponibile nello standard C ++ 11 per i tipi complicati di modelli, che è ciò per cui credo sia stato progettato. Ma lo sto usando anche per cose come:

auto foo = std::make_shared<Foo>();

E più scettico per:

auto foo = bla(); // where bla() return a shared_ptr<Foo>

Non ho visto molte discussioni su questo argomento. Sembra che autopotrebbe essere abusato, dal momento che un tipo è spesso una forma di documentazione e controlli di integrità. Dove si disegna la linea nell'uso autoe quali sono i casi d'uso consigliati per questa nuova funzionalità?

Per chiarire: non sto chiedendo un'opinione filosofica; Sto chiedendo l'uso previsto di questa parola chiave da parte del comitato standard, possibilmente con commenti su come quell'uso previsto viene realizzato nella pratica.

Nota a margine: questa domanda è stata spostata in SE.Programmers e quindi di nuovo in Stack Overflow. La discussione su questo può essere trovata in questa meta domanda .


Questo è un sito di domande e risposte, non un sito di discussione. Hai fatto una domanda molto generica e dubito che qualcuno sarà in grado di darti qualcosa di diverso da quello altamente soggettivo. (ecco perché -1)
TravisG il

15
@heishe, ho aggiunto un chiarimento. Se leggi la domanda molto in generale, sembra che stia chiedendo un'opinione soggettiva, ma davvero se hai usato la autoparola chiave, allora sai come dovrebbe essere usata. Questo è quello che sto chiedendo, come qualcuno che è nuovo di questa funzione, è come dovrei usarlo?
Alan Turing,

13
Ho visto questa discussione dappertutto quando è stato introdotto C # var(ovvero, una volta che la gente ha avuto l'idea che dopo tutto non si trattava di una digitazione dinamica). Se lo desideri, puoi iniziare con questa domanda e passare attraverso le domande correlate.
R. Martinho Fernandes,

2
@Lex: o qualcosa è legale o non lo è; chiamare qualcosa di "cattivo" che è legale è soggettivo per definizione. Vale a dire, chiamando auto foo = bla();"cattivo" è chiaramente un parere, non un fatto, che rende questa domanda e rispondere ad una discussione, che lo rende rilevante per programmatori SE, che è esattamente ciò che gli stretti voti indicano. / scrollata di spalle
ildjarn,

3
Herb Sutter su questo argomento: herbsutter.com/2013/06/13/…
rafak

Risposte:


131

Penso che si dovrebbe usare la autoparola chiave ogni volta che è difficile dire come scrivere il tipo a prima vista, ma il tipo del lato destro di un'espressione è ovvio. Ad esempio, usando:

my_multi_type::nth_index<2>::type::key_type::composite_key_type::
    key_extractor_tuple::tail_type::head_type::result_type

per ottenere il tipo di chiave composita boost::multi_index, anche se sai che lo è int. Non puoi semplicemente scrivere intperché potrebbe essere cambiato in futuro. Vorrei scrivere autoin questo caso.

Quindi, se la autoparola chiave migliora la leggibilità in un caso particolare, allora usala. Puoi scrivere autoquando è ovvio per il lettore quale tipo autorappresenta.

Ecco alcuni esempi:

auto foo = std::make_shared<Foo>();   // obvious
auto foo = bla();                     // unclear. don't know which type `foo` has

const size_t max_size = 100;
for ( auto x = max_size; x > 0; --x ) // unclear. could lead to the errors
                                      // since max_size is unsigned

std::vector<some_class> v;
for ( auto it = v.begin(); it != v.end(); ++it )
                                      // ok, since I know that `it` has an iterator type
                                      // (don't really care which one in this context)

18
Non sembra una buona raccomandazione. Quanto spesso non conosci il tipo di oggetto? (Al di fuori dei modelli, cioè.) E se non sai che digitano, cerca, non essere pigro e usa auto.
Paul Manta,

9
@Paul: spesso le volte che conosci o hai solo bisogno di conoscere la parte più importante del tipo, ad esempio che è un iteratore, ma non sai e non ti importa se è un iteratore di vettori o iteratore di link elenco; in quei casi, non vuoi davvero perdere tempo e spazio sullo schermo cercando di capire come scrivere i tipi.
Lie Ryan,

45
Indipendentemente dal fatto che gli irriducibili del C ++ lo apprezzino o meno, il C ++ 0x attirerà persone che non avrebbero mai usato il C ++. E quelli useranno auto dappertutto.
Prof. Falken,

6
@ R.MartinhoFernandes - no, l'unica cosa che è chiara è che qualunque cosa bla() tu restituisca, la stai dando foo.
Luis Machuca,

8
@LuisMachuca la mia risposta è stata un modo ironico di dire che è disonesto dare un libro di testo di esempio di variabile errata e denominazione di funzioni e incolpare la sua mancanza di leggibilità sull'inferenza del tipo.
R. Martinho Fernandes,

62

Utilizzare autoovunque sia possibile, in particolare in const automodo che gli effetti collaterali siano meno preoccupanti. Non dovrai preoccuparti dei tipi tranne nei casi ovvi, ma saranno comunque verificati staticamente per te e puoi evitare qualche ripetizione. Dove autonon è possibile, è possibile utilizzare decltypeper esprimere i tipi semanticamente come contratti basati su espressioni. Il tuo codice apparirà diverso, ma sarà un cambiamento positivo.


12
Direi in particolare "const auto &"
Viktor Sehr il

2
Migliore utilizzo auto&&in situazioni complesse edmundv.home.xs4all.nl/blog/2014/01/28/…
KindDragon

2
@KindDragon: Questa è una buona regola empirica, ma preferisco usare const auto&o const autose non voglio esplicitamente mutare o muovermi.
Jon Purdy,

" Usa auto ovunque puoi ". Questo significa che dovrei scrivere auto str = std::string();invece di std::string str;?
Calmarius

4
L'uso di auto ovunque ti renderà il tuo codice meno leggibile e più difficile da eseguire il debug perché dovrai dedurre tu stesso i tipi durante la lettura del codice.
Sottomacchina

52

Facile. Usalo quando non ti interessa quale sia il tipo. Per esempio

for (const auto & i : some_container) {
   ...

Tutto ciò che mi interessa qui è che isia tutto ciò che è nel contenitore.

È un po 'come i typedef.

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

Qui, non mi importa se he wsono galleggianti o doppi, solo che sono di qualsiasi tipo adatto ad esprimere altezze e pesi .

O considera

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

Qui tutto ciò che mi interessa è che sia un iteratore adatto, che supporti operator++(), è un po 'come scrivere un'anatra in questo senso.

Anche il tipo di lambda non può essere scritto, quindi auto f = []...è di buon stile. L'alternativa è lanciare a std::functionma che arriva con overhead.

Non riesco davvero a concepire un "abuso" di auto. Il più vicino che posso immaginare è privarti di una conversione esplicita in un tipo significativo - ma non lo utilizzeresti autoper questo, costruiresti un oggetto del tipo desiderato.

Se riesci a rimuovere un po 'di ridondanza nel tuo codice senza introdurre effetti collaterali, allora deve essere buono farlo.

Controesempi (presi in prestito dalle risposte di qualcun altro):

auto i = SomeClass();
for (auto x = make_unsigned (y); ...)

Qui ci interessa quale sia il tipo, quindi dovremmo scrivere Someclass i;efor(unsigned x = y;...


1
Um. Non così semplice. Si compila e funziona e ti sei appena sparato nel piede se gli oggetti sono oggetti non banali: la tua iterazione chiama il costruttore e il distruttore della copia in ogni fase dell'iterazione. Se utilizzerai ciecamente auto in un iteratore basato su intervallo, probabilmente dovrebbe essere "for (const auto & item: some_container)" anziché "for (auto item: some_container)".
Don Hatch,

2
Non sempre, ma ok probabilmente vuoi riferimenti. E allora? Non c'entra nienteauto .
spruzzo

Davvero non capisco il tuo ultimo commento. Ho cercato di spiegare perché la tua strategia non mi sembra molto valida e perché la sto ridimensionando.
Don Hatch,

5
Sto dicendo che se usi riferimenti o meno è ortogonale al fatto che usi auto o meno. Lo modificherò per aggiungere il riferimento perché è vero che di solito è quello che si vuole fare, ma è del tutto irrilevante per l'argomento in questione.
spruzzo

43

Fallo. Utilizzare autoovunque semplifica la scrittura del codice.

Ogni nuova funzionalità in qualsiasi lingua verrà abusata da almeno alcuni tipi di programmatori. È solo attraverso un uso eccessivo moderato da parte di alcuni programmatori esperti (non noob) che il resto dei programmatori esperti apprende i limiti del corretto utilizzo. L'uso eccessivo di solito è negativo, ma potrebbe essere buono perché tale uso eccessivo può portare a miglioramenti nella funzionalità o a una migliore funzionalità per sostituirla.

Ma se stessi lavorando su un codice con più di alcune righe come

auto foo = bla();

dove il tipo è indicato zero volte, potrei voler cambiare quelle righe per includerne i tipi. Il primo esempio è ottimo poiché il tipo viene dichiarato una volta e autoci evita di dover scrivere due tipi di modelli disordinati due volte. Evviva per C ++++. Ma mostrare esplicitamente il tipo zero volte, se non è facilmente visibile in una linea vicina, mi rende nervoso, almeno in C ++ e nei suoi successori immediati. Per altre lingue progettate per funzionare a un livello superiore con più astrazione, polimorfismo e genericità, va bene.


38

A C ++ e Beyond 2012 nel pannello Ask Us Anything , c'è stato un fantastico scambio tra Andrei Alexandrescu, Scott Meyers e Herb Sutter che parlavano di quando usare e non usareauto . Passa al minuto 25:03 per una discussione di 4 minuti. Tutti e tre i diffusori forniscono punti eccellenti da tenere a mente per quando non utilizzarli auto.

Incoraggio vivamente le persone a giungere alle proprie conclusioni, ma il mio take away era di usarlo autoovunque a meno che :

  1. Fa male la leggibilità
  2. Esiste preoccupazione per la conversione automatica dei tipi (ad es. Da costruttori, assegnazione, tipi intermedi di template, conversione implicita tra larghezze intere)

L'uso liberale di explicitaiuta a ridurre le preoccupazioni per quest'ultimo, il che aiuta a ridurre al minimo il tempo che il primo rappresenta un problema.

Riformulando ciò che Herb ha detto, "se non stai facendo X, Y e Z, usa auto. Scopri cosa sono X, Y e Z e vai avanti e usaauto altrove".


4
Probabilmente vale anche la pena collegarlo a "quasi sempre auto" - herbsutter.com/2013/08/12/…
Rob Starling

37

Sì, può essere abusato a scapito della leggibilità. Suggerisco di usarlo nei contesti in cui i tipi esatti sono lunghi, non indicibili o non importanti per la leggibilità e le variabili hanno vita breve. Ad esempio, il tipo di iteratore di solito è lungo e non è importante, quindi autofunzionerebbe:

   for(auto i = container.begin(); i != container.end(); ++i);

auto qui non danneggia la leggibilità.

Un altro esempio è il tipo di regola del parser, che può essere lungo e contorto. Confrontare:

   auto spaces = space & space & space;

con

r_and_t<r_and_t<r_char_t<char>&, r_char_t<char>&>, r_char_t<char>&> spaces = 
   space & space & space;

D'altra parte, quando il tipo è noto ed è semplice, è molto meglio se affermava esplicitamente:

int i = foo();

piuttosto che

auto i = foo();

2
Ovviamente, avere un range-based per loop nella lingua rende il tuo primo esempio meno emozionante. 8v)
Fred Larson il

@Fred: il tipo può ancora essere ingombrante (sto pensando a contenitori associativi)
Matthieu M.

3
@Fred: ogni volta che i tuoi limiti non lo sono begin()e end(), o la dimensione del tuo passo è diversa da una, o stai modificando il contenitore mentre esegui il ciclo, la dichiarazione basata sull'intervallo non ti aiuterà.
Dennis Zickefoose,

6
@geotavros: E lo r_and_t<r_and_t<r_char_t<char>&, r_char_t<char>&>, r_char_t<char>&>fa?
Dennis Zickefoose,

7
@geotavros: oppure puoi vedere di che tipo spaceè e cercarlo. Sono comunque le informazioni più utili ... dopo tutto, il problema non è "che tipo è questa nuova variabile" ma piuttosto "che cosa space & space & spacesignifica?" Il tipo reale dell'espressione è solo rumore.
Dennis Zickefoose,

19

auto può essere molto pericoloso in combinazione con i modelli di espressione che sono molto usati dalle librerie di algebra lineare come Eigen o OpenCV.

auto A = Matrix(...);
auto B = Matrix(...);
auto C = A * B; // C is not a matrix. It is a matrix EXPRESSION.
cout << C; // The expression is evaluated and gives the expected result.
... // <code modifying A or B>
cout << C; // The expression is evaluated AGAIN and gives a DIFFERENT result.

I bug causati da questo tipo di errori sono un grande dolore per il debug. Un possibile rimedio è quello di trasmettere esplicitamente il risultato al tipo previsto se si è intenzionati a usare auto per lo stile di dichiarazione da sinistra a destra.

auto C = Matrix(A * B); // The expression is now evaluated immediately.

1
Sembra uno strano comportamento per cominciare. Se sto moltiplicando due matrici, anche se l'operazione è pigra, non mi aspetto che sia rivalutabile, mi aspetto che mantenga il suo stato valutato dopo la valutazione iniziale. Se vuoi cambiare i parametri senza modificare i parametri originali, non dovresti comunque ricostruire l'espressione? O è progettato per una sorta di situazione di elaborazione in streaming, in cui i parametri originali cambiano costantemente ma la procedura rimane la stessa?
JAB

1
Indipendentemente dal fatto che l' A*Bespressione sia copiata in una autovariabile o qualcos'altro, il comportamento che descrivi è ancora presente.
xtofl,

Nessun errore è causato dall'utilizzo auto.
Jim Balter,

@JAB: è progettato per supportare semplificazioni e sinergie tra le operazioni, ad esempio diag(A * B)non deve sprecare cicli calcolando gli elementi off-diagonali.
Ben Voigt,

Ho anche individuato codice come 'for (auto o: vecContainer)' dove 'o' era un oggetto pesante che veniva copiato ogni volta. La mia raccomandazione sarebbe quella di usarlo per quello che era originariamente inteso, cioè modelli (con linee guida di deduzione difficili) e laboriosi typedef. Anche allora devi essere attento e distinto tra auto e auto &.
gast128

10

Uso autosenza restrizioni e non ho riscontrato alcun problema. A volte finisco anche per usarlo per tipi semplici come int. Questo rende c ++ un linguaggio di livello superiore per me e permette di dichiarare variabili in c ++ come in Python. Dopo aver scritto il codice Python, a volte scrivo anche ad es

auto i = MyClass();

invece di

MyClass i;

Questo è un caso in cui direi che è un abuso di auto parola chiave.

Spesso non mi importa quale sia il tipo esatto dell'oggetto, sono più interessato alla sua funzionalità, e poiché i nomi delle funzioni generalmente dicono qualcosa sugli oggetti che restituiscono, autonon fa male: in ad esempio auto s = mycollection.size(), posso immaginare che ssarà una specie di numero intero, e nel raro caso in cui mi preoccupo del tipo esatto, controlliamo quindi il prototipo della funzione (voglio dire, preferisco controllare quando ho bisogno delle informazioni, piuttosto che a priori quando il codice è scritto, solo nel caso in cui sarebbe utile un giorno, come in int_type s = mycollection.size()).

Per quanto riguarda questo esempio dalla risposta accettata:

for ( auto x = max_size; x > 0; --x )

Nel mio codice uso ancora autoin questo caso, e se voglio xessere senza segno, quindi uso una funzione di utilità, chiamata say make_unsigned, che esprime chiaramente le mie preoccupazioni:

for ( auto x = make_unsigned(max_size); x > 0; --x )

disclaimer: descrivo solo il mio uso, non sono competente a dare consigli!


1
@ChristianRau: non era sarcastico. Si noti che non ho raccomandato l'uso auto i = MyClass().
Rafak,

3
Seguito: vedi: herbsutter.com/2013/06/13/… . L'uso di eg as_unsignedè raccomandato lì, o anche auto w = widget{};.
rafak,

3

Uno dei maggiori problemi con il programma C ++ è che ti permette di usare la variabile non inizializzata . Questo ci porta a un cattivo comportamento del programma non deterministico. Va notato che il compilatore moderno ora genera messaggi di avviso appropriati / messaggio se il programma si stanca di usarlo.

Giusto per illustrare questo, considera di seguito il programma c ++:

int main() {
    int x;
    int y = 0;
    y += x;
}

Se compilo questo programma usando il compilatore moderno (GCC), dà l'avvertimento. Tale avvertimento potrebbe non essere molto ovvio se stiamo lavorando con un codice di produzione davvero complesso.

main.cpp: nella funzione 'int main ()':

main.cpp: 4: 8: warning : 'x' viene usato non inizializzato in questa funzione [-Wuninitialized]

y + = x;

    ^

================================================== =============================== Se ora cambiamo il nostro programma che utilizza auto , quindi compiliamo otteniamo quanto segue:

int main() {
    auto x;
    auto y = 0;
    y += x;
}

main.cpp: nella funzione 'int main ()':

main.cpp: 2: 10: errore : la dichiarazione di 'auto x' non ha inizializzatori

 auto x;

      ^

Con auto, non è possibile utilizzare la variabile non inizializzata. Questo è un grande vantaggio che potremmo ottenere (gratuitamente) se iniziamo a usare auto .

Questo concetto e altri grandiosi concetti moderni di C ++ sono spiegati dall'esperto di C ++ Herb Shutter nel suo discorso su CppCon14 :

Tornando alle basi! Elementi essenziali del moderno stile C ++


2
Sì. E per specificare il tipo è possibile inizializzare come 0i, 0u, 0l, 0ul, 0.0f, 0.0 o anche int (), unsigned (), double (), ecc.
Robinson

6
Questo argomento è ridicolo. Stai dicendo "non puoi dimenticare l'inizializzatore perché otterrai un errore del compilatore", ma questo richiede di ricordarti di usare auto.
Razze di leggerezza in orbita

1
@LightnessRacesinOrbit: ho capito (appreso) questo concetto dai discorsi di Herb Sutter. Ho trovato consigli logici / pratici dai discorsi sulle erbe, quindi ho pensato di condividere con la comunità.
Mantosh Kumar,

2
Non penso che questa sia una buona motivazione per usare l'auto. Basta attivare il flag di avviso del compilatore per l'uso di variabili non inizializzate e indirizzare tutti gli avvisi. Questo non vuol dire che non dovresti usare auto- ma non ti serve per risolvere questo problema.
einpoklum,

2

Un pericolo che ho notato è in termini di riferimenti. per esempio

MyBigObject& ref_to_big_object= big_object;
auto another_ref = ref_to_big_object; // ?

Il problema è che another_ref non è in realtà un riferimento in questo caso, è MyBigObject anziché MyBigObject &. Finisci per copiare un grande oggetto senza accorgertene.

Se stai ricevendo un riferimento direttamente da un metodo potresti non pensare a cosa sia effettivamente.

auto another_ref = function_returning_ref_to_big_object();

avresti bisogno di "auto &" o "const auto &"

MyBigObject& ref_to_big_object= big_object;
auto& another_ref = ref_to_big_object;
const auto& yet_another_ref = function_returning_ref_to_big_object();

1

Uso auto dove ha senso dedurre un tipo. Se hai qualcosa che sai essere un numero intero, o sai che è una stringa, usa semplicemente int / std :: string, ecc. o offusca il codice.

Questa è comunque la mia opinione.


4
"dove ha senso ..." è la parte difficile. I programmatori possono rimanere così coinvolti nei dettagli della codifica, che perdono il senso di ciò che ha senso per altri programmatori in particolari manutentori futuri.
DarenW,

È vero, anche se penso che sia abbastanza facile dire quando è ambiguo. In caso di dubbi, utilizzare un commento!
LainIwakura,

1
Non avrebbe senso usare un tipo?
Mikhail,

Alcuni sviluppatori direbbero che il tipo dovrebbe sempre essere dedotto!
Arafangion,

Usa un commento ...... o semplicemente usa il tipo e non hai bisogno di commenti?
user997112

1

autola parola chiave può essere utilizzata solo per la variabile locale, non per argomenti o membri class / struct. Quindi, è sicuro e fattibile usarli ovunque tu voglia. Li uso molto. Il tipo viene dedotto al momento della compilazione, il debugger mostra il tipo durante il debug, lo sizeofriporta correttamente, decltypedarebbe il tipo corretto - non vi è alcun danno. Non considero automai abusato!


0

TL; DR: vedere la regola empirica in basso.

La risposta accettata suggerisce la seguente regola empirica:

Usare autoogni volta che è difficile dire come scrivere il tipo a prima vista, ma il tipo del lato destro di un'espressione è ovvio.

Ma direi che è troppo restrittivo. A volte non mi interessano i tipi, dal momento che l'affermazione è abbastanza istruttiva senza che mi prenda il tempo di dedicare il tempo a capire il tipo. Cosa intendo con questo? Considera l'esempio che è emerso in alcune delle risposte:

auto x = f();

Cosa rende questo un esempio di uso improprio di auto? È la mia ignoranza sul f()tipo di ritorno? Bene, potrebbe davvero aiutare se lo sapessi, ma - questa non è la mia principale preoccupazione. Ciò che è molto più di un problema è che xe f()sono abbastanza insignificante. Se avessimo:

auto nugget = mine_gold();

invece, di solito non mi interessa se il tipo di ritorno della funzione è ovvio o meno. Leggendo la dichiarazione, so cosa sto facendo e so abbastanza su quale sia la semantica del valore di ritorno da non sentire che ho bisogno di conoscere anche il suo tipo.

Quindi la mia risposta è: utilizzare autoogni volta che il compilatore lo consente, a meno che:

  • Ritieni che il nome della variabile insieme all'espressione di inizializzazione / assegnazione non fornisca informazioni sufficienti su ciò che sta facendo l'istruzione.
  • Ritieni che il nome della variabile insieme all'espressione di inizializzazione / assegnazione fornisca informazioni "fuorvianti" su quale dovrebbe essere il tipo - ovvero, se dovessi indovinare cosa viene fornito al posto dell'auto, saresti in grado di indovinare - e sarebbe sbagliato, e questo falso presupposto ha ripercussioni più avanti nel codice.
  • Vuoi forzare un tipo diverso (ad esempio un riferimento).

E anche:

  • Preferisci dare un nome significativo (che non contenga naturalmente il nome del tipo) prima di sostituirlo autocon il tipo concreto.

-3

Una delle mie amare esperienze autoè usarla con espressioni lambda :

auto i = []() { return 0; };
cout<<"i = "<<i<<endl; // output: 1 !!!

In realtà, qui iè stato risolto il funzionamento del puntatore di int(*)(). Questo è solo un semplice cout, ma immagina che tipo di errori di compilazione / runtime può causare quando utilizzato template.

Dovresti evitare auto con tali espressioni e mettere un returntipo corretto (o controllato decltype())

L'uso corretto per l'esempio sopra sarebbe,

auto i = []() { return 0; }(); // and now i contains the result of calling the lambda  

7
Hai fatto un lavoro terribile nello spiegare cosa ha a che fare con l'auto. Hai creato una funzione e poi l'hai stampata ... Va bene?
Dennis Zickefoose,

5
@iammilind: e cosa c'entra questo con l'auto?
R. Martinho Fernandes,

7
Trovo altamente improbabile che uno voglia mettere ()alla fine. Le lambda sono lì per agire come funzioni ed è da lì che viene la conversione del puntatore a funzione. Se vuoi chiamarlo subito, perché usare un lambda? auto i = 0;funziona piuttosto bene.
R. Martinho Fernandes

4
Puoi almeno descrivere uno scenario in cui auto x = []() { /* .... whatever goes in here ... */ }()è meglio auto x = /* .... whatever goes in here ... */;, cioè, la stessa cosa, senza la lambda? Lo trovo piuttosto inutile, per lo stesso motivo auto x = 42 + y - 42è inutile.
R. Martinho Fernandes

6
-1 questo non è colpa di auto. Il tipo di lambda non può essere scritto quindi è necessario auto , se ti dimentichi di chiamare la funzione, allora è il tuo belvedere! È possibile inserire un puntatore a funzione non chiamata in una C printf in modo altrettanto casuale.
spruzzo
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.