Molta enfasi di recente è stata posta sul nuovo uso della keyword auto in C++11 (si veda per esempio qui). In sintesi, auto sostituisce un tipo esplicito con una richiesta rivolta al compilatore di riempire con l’informazione corretta:
std::vector<double> v;
auto it = v.begin();
Il frammento sopra afferma: lascio al compilatore la deduzione del tipo di it.
La maggior parte dei commenti però tende a enfatizzare i casi in cui usare auto, ma il vero problema è quando non usarlo. Siccome la keyword risparmia fatica al programmatore, ricordare continuamente “usate auto qui, usate auto là” porta facilmente a pensare che vada usato sempre, e diventa abbastanza naturale abusarne. auto però non è gratis: è fondamentale che il codice esprima correttamente l’intento del programmatore. se l’intento è chiaro, i bug diventano evidenti e si possono correggere facilmente. ma ci sono casi in cui l’uso di auto nasconde l’intento:
- rileggendo il codice a distanza di tempo, diventa più difficile capire cosa sta succedendo (soprattutto se tutte le variabili locali sono auto… caso realmente accaduto)
- ci sono casi in cui un cast viene involontariamente eliminato: nell’esempio semplificato sopra, it poteva essere const_iterator, ma il programmatore intendeva dire auto it = v.cbegin() oppure const_iterator it = v.begin()?.
- (caso particolare del punto precedente) alcuni container restituiscono dei proxy, e l’uso indiscriminato di auto può rompere del codice funzionante
bool f1()
{
std::vector<bool>* vp = new std::vector<bool>(1000, true);
bool y = (*vp)[314]; // ok
delete vp;
return y;
}
bool f2()
{
std::vector<bool>* vp = new std::vector<bool>(1000, true);
auto y = (*vp)[314]; // mmm...
delete vp;
return y; // argh! il proxy potrebbe leggere il container già distrutto
}
- un IDE che fa un parsing euristico potrebbe non essere più in grado di elencare correttamente tutti i punti in cui un tipo viene usato; a volte il completamento automatico non funziona più. si pensi ad esempio a:
class ABC
{
int size() const;
};
ABC GimmeMyObject();
// molto più tardi...
auto abc = GimmeMyObject();
auto n = GimmeMyObject().size();
Durante il refactoring, si vogliono trovare tutti gli oggetti di tipo ABC; normalmente basta una ricerca di testo (ci possono essere mille motivi: progetto troppo grosso, o stiamo usando un modem a 56k e vi…), ma se la variabile è auto, ci vuole un IDE più sofisticato e ben integrato con il compilatore.
In sintesi, è una buona idea usare auto quando:
- chiunque è in grado di dedurre il tipo senza saltellare attraverso il codice, vuoi per il nome della variabile, vuoi per la semplicità dell’inizializzazione (cfr. esempio #1). Di solito, il tipo è un nome dipendente e lunghissimo (std::map<std::string, std::list<double>, MySpecialComparisonOperator, MyCustomAllocator>::const_iterator…); questa è una buona indicazione per usare auto.
- il tipo della variabile potrebbe cambiare in qualsiasi momento, mantenendo la stessa interfaccia. Nell’esempio #2, si pensi che le prime righe siano in realtà generate da un programma esterno che emette codice c++ (ad esempio, questo). Il tipo esatto di “n” potrebbe variare semplicemente aggiornando il programma esterno, ma il cambiamento potrebbe non essere rilevante (spesso basta che sia n un intero con certe proprietà);
- quando c’è un limite di 80 caratteri per riga (ma suvvia… siamo nel 2013, chi mai segue una regola del genere?).
Per enfatizzare che il nostro scopo è scoraggiare criticamente, riportiamo anche i casi contrari: è una cattiva idea usare auto quando:
- l’inizializzazione non è ovvia, ovvero solo guardando cosa c’è a destra dell’= non è possibile dedurre il tipo della variabile. auto significa “lascio la deduzione al compilatore”, ma non “lascio la deduzione al compilatore… perché io non la so fare” (questo si applica anche alle somme di interi di tipo diverso, p.es. short + unsigned char)
- c’è un cast di mezzo
auto x = static_cast<int>(GetNumberAsDouble()); // mmm... l'intento è chiaro, ma il codice è contorto
- c’è un proxy: auto rischia di tenere in vita degli oggetti che non sono pensati per sopravvivere a lungo
- si esagera! non è il caso di iniziare un sorgente con:
auto main(auto argc, const auto* argv[]) -> int // uhm... forse in c++2075...
Io auto nei cast lo consiglierei. Per esmpio pensiamo di avere una classe astratta MessageResponse e di doverla castarla in una class concretta:
const auto& exitMessage = static_cast< const ExitMessageResponse& >( *genericMessage )
senza auto diventerebbe:
const ExitMessageResponse& exitMessage = static_cast< const ExitMessageResponse& >( *genericMessage )