J'ai vu une conférence par Herb Sutter où il encourage chaque programmeur C++ à utiliser auto
.
J'ai dû lire le code C # il y a quelque temps où var
était largement utilisé et le code était très difficile à comprendre - chaque fois que var
était utilisé, je devais vérifier le type de retour du côté droit. Parfois plus d'une fois, car j'ai oublié le type de la variable après un certain temps!
Je sais que le compilateur connaît le type et je n'ai pas à l'écrire, mais il est largement admis que nous devrions écrire du code pour les programmeurs, pas pour les compilateurs.
Je sais aussi que c'est plus facile à écrire:
auto x = GetX();
Que:
someWeirdTemplate<someOtherVeryLongNameType, ...>::someOtherLongType x = GetX();
Mais ceci n'est écrit qu'une seule fois et le type de retour GetX()
est vérifié plusieurs fois pour comprendre quel type x
a.
Cela m'a fait me demander: auto
rend-il le code C++ plus difficile à comprendre?
Réponse courte: Plus complètement, mon opinion actuelle sur auto
est que vous devez utiliser auto
par défaut sauf si vous voulez explicitement une conversion. (Un peu plus précisément, "... sauf si vous voulez vous engager explicitement dans un type, ce qui est presque toujours parce que vous voulez une conversion.")
Réponse plus longue et justification:
N'écrivez un type explicite (plutôt que auto
) que lorsque vous voulez vraiment vous engager explicitement dans un type, ce qui signifie presque toujours que vous voulez obtenir explicitement une conversion vers ce type. Du haut de ma tête, je me souviens de deux cas principaux:
initializer_list
Que auto x = { 1 };
Déduit initializer_list
. Si vous ne voulez pas initializer_list
, Dites le type - c'est-à-dire, demandez explicitement une conversion.auto x = matrix1 * matrix 2 + matrix3;
Capture un type d'assistance ou de proxy non destiné à être visible par le programmeur. Dans de nombreux cas, il est bien et bénin de capturer ce type, mais parfois si vous voulez vraiment qu'il s'effondre et fasse le calcul, dites le type - c'est-à-dire, demandez à nouveau explicitement une conversion.Par défaut, utilisez auto
par défaut, car l'utilisation de auto
évite les pièges et rend votre code plus correct, plus facile à maintenir et robuste, et plus efficace. Grosso modo du plus important au moins important, dans un esprit "écrivez d'abord pour la clarté et l'exactitude":
auto
garantit que vous obtiendrez le bon type. Comme dit le proverbe, si vous vous répétez (dites le type de manière redondante), vous pouvez mentir et vous mentir (vous tromper). Voici un exemple habituel: void f( const vector<int>& v ) { for( /*…*
- à ce stade, si vous écrivez explicitement le type de l'itérateur, vous voulez vous rappeler d'écrire const_iterator
(L'avez-vous fait?), Tandis que auto
fait juste les choses.auto
rend votre code plus robuste face au changement, car lorsque le type de l'expression change, auto
continuera à se résoudre au type correct. Si vous vous engagez à la place sur un type explicite, la modification du type de l'expression injectera des conversions silencieuses lorsque le nouveau type se convertira en ancien type, ou des interruptions de construction inutiles lorsque le nouveau type fonctionne toujours comme l'ancien type mais ne se convertit pas à l'ancien type (par exemple, lorsque vous changez un map
en un unordered_map
, ce qui est toujours bien si vous ne comptez pas sur la commande, en utilisant auto
pour vos itérateurs, vous passez de map<>::iterator
à unordered_map<>::iterator
en toute transparence, mais l'utilisation de map<>::iterator
partout signifie explicitement que vous perdrez votre temps précieux sur une ondulation de correction de code mécanique, à moins qu'un stagiaire ne passe et vous pouvez leur imposer le travail ennuyeux).auto
garantit qu'aucune conversion implicite ne se produira, il garantit de meilleures performances par défaut. Si au lieu de cela vous dites le type, et qu'il nécessite une conversion, vous obtiendrez souvent silencieusement une conversion, que vous vous y attendiez ou non.auto
est votre seule bonne option pour les types difficiles à épeler et inexprimables, tels que les lambdas et les aides de modèle, bref de recourir à des expressions decltype
répétitives ou à des indirections moins efficaces comme std::function
.auto
est moins de frappe. Je mentionne ce dernier pour être complet car c'est une raison courante de l'aimer, mais ce n'est pas la plus grande raison de l'utiliser.Par conséquent: préférez dire auto
par défaut. Il offre tellement de simplicité, de performances et de clarté que vous ne vous blessez (ainsi que les futurs responsables de votre code) si vous ne le faites pas. N'engagez un type explicite que lorsque vous le pensez vraiment, ce qui signifie presque toujours que vous voulez une conversion explicite.
Oui, il y a (maintenant) un GotW à ce sujet.
C'est une situation au cas par cas.
Cela rend parfois le code plus difficile à comprendre, parfois non. Prenez, par exemple:
void foo(const std::map<int, std::string>& x)
{
for ( auto it = x.begin() ; it != x.end() ; it++ )
{
//....
}
}
est certainement facile à comprendre et nettement plus facile à écrire que la déclaration réelle de l'itérateur.
J'utilise C++ depuis un certain temps maintenant, mais je peux garantir que j'obtiendrai une erreur de compilation à mon premier coup, car j'oublierais le const_iterator
et opterait initialement pour le iterator
... :)
Je l'utiliserais pour des cas comme celui-ci, mais pas là où il obscurcit réellement le type (comme votre situation), mais c'est purement subjectif.
Regardez-le d'une autre manière. Écris-tu:
std::cout << (foo() + bar()) << "\n";
ou:
// it is important to know the types of these values
int f = foo();
size_t b = bar();
size_t total = f + b;
std::cout << total << "\n";
Parfois, il n'est pas utile de préciser explicitement le type.
La décision de mentionner le type n'est pas la même que la décision de diviser le code sur plusieurs instructions en définissant des variables intermédiaires. En C++ 03, les deux étaient liés, vous pouvez penser à auto
comme un moyen de les séparer.
Parfois, rendre les types explicites peut être utile:
// seems legit
if (foo() < bar()) { ... }
vs.
// ah, there's something tricky going on here, a mixed comparison
if ((unsigned int)foo() < bar()) { ... }
Dans les cas où vous déclarez une variable, l'utilisation de auto
permet au type de ne pas être prononcé comme il l'est dans de nombreuses expressions. Vous devriez probablement essayer de décider par vous-même quand cela aide à la lisibilité et quand cela entrave.
Vous pouvez affirmer que mélanger les types signés et non signés est une erreur pour commencer (en fait, certains soutiennent en outre qu'il ne faut pas utiliser du tout les types non signés). La raison c'est sans doute une erreur, c'est qu'elle rend les types d'opérandes d'une importance vitale en raison du comportement différent. Si c'est une mauvaise chose d'avoir besoin de connaître les types de vos valeurs, alors ce n'est probablement pas aussi une mauvaise chose de ne pas avoir besoin de les connaître. Donc, à condition que le code ne soit pas déjà déroutant pour d'autres raisons, cela rend auto
OK, non? ;-)
En particulier, lors de l'écriture de code générique, il existe des cas où le type réel d'une variable ne devrait pas être important, ce qui compte, c'est qu'il satisfasse l'interface requise. Donc auto
fournit un niveau d'abstraction où vous ignorez le type (mais bien sûr, le compilateur ne le sait pas). Travailler à un niveau d'abstraction approprié peut beaucoup aider à la lisibilité, travailler au "mauvais" niveau fait de la lecture du code un slog.
OMI, vous regardez cela à peu près à l'envers.
Il ne s'agit pas de auto
menant à un code illisible ou encore moins lisible. C'est une question (en espérant que) d'avoir un type explicite pour la valeur de retour compensera le fait que (apparemment) on ne sait pas quel type serait retourné par une fonction particulière.
Du moins à mon avis, si vous avez une fonction dont le type de retour n'est pas immédiatement évident, c'est votre problème ici. Ce que fait la fonction doit être évident d'après son nom, et le type de la valeur de retour doit être évident d'après ce qu'elle fait. Sinon, c'est la véritable source du problème.
S'il y a un problème ici, ce n'est pas avec auto
. C'est avec le reste du code, et les chances sont assez bonnes que le type explicite soit juste assez d'un pansement pour vous empêcher de voir et/ou de résoudre le problème principal. Une fois que vous avez résolu ce problème réel, la lisibilité du code en utilisant auto
sera généralement très bien.
Je suppose en toute justice que je devrais ajouter: j'ai traité de quelques cas où de telles choses n'étaient pas aussi évidentes que vous le souhaiteriez, et la résolution du problème était également assez intenable. Juste pour un exemple, j'ai fait du conseil pour une entreprise il y a quelques années qui avait auparavant fusionné avec une autre entreprise. Ils se sont retrouvés avec une base de code qui était plus "rapprochée" que vraiment fusionnée. Les programmes constituants avaient commencé à utiliser des bibliothèques différentes (mais assez similaires) à des fins similaires, et bien qu'ils s'efforcent de fusionner les choses plus proprement, ils le faisaient toujours. Dans bon nombre de cas, la seule façon de deviner quel type serait retourné par une fonction donnée était de savoir d'où venait cette fonction.
Même dans un tel cas, vous pouvez aider à clarifier un certain nombre de choses. Dans ce cas, tout le code a commencé dans l'espace de noms global. Le simple fait de déplacer une bonne quantité dans certains espaces de noms a éliminé les conflits de noms et a également facilité le suivi de type.
Il y a plusieurs raisons pour lesquelles je n'aime pas l'automobile pour une utilisation générale:
Mais attendez, est-ce vraiment une bonne idée? Et si le type importait dans une demi-douzaine de ces cas d'utilisation, et maintenant que le code se comporte réellement différemment? Cela peut également implicitement rompre l'encapsulation, en modifiant non seulement les valeurs d'entrée, mais le comportement lui-même de l'implémentation privée d'autres classes qui appellent la fonction.
1a. Je suis partisan du concept de "code auto-documenté". Le raisonnement derrière le code auto-documenté est que les commentaires ont tendance à devenir obsolètes, ne reflétant plus ce que fait le code, alors que le code lui-même - s'il est écrit de manière explicite - est explicite, reste toujours à jour sur son intention, et ne vous laissera pas confondu avec des commentaires périmés. Si les types peuvent être modifiés sans avoir à modifier le code lui-même, le code/les variables eux-mêmes peuvent devenir périmés. Par exemple:
auto bThreadOK = CheckThreadHealth ();
Sauf que le problème est que CheckThreadHealth () à un moment donné a été refactorisé pour renvoyer une valeur d'énumération indiquant l'état d'erreur, le cas échéant, au lieu d'un booléen. Mais la personne qui a effectué cette modification n'a pas inspecté cette ligne de code particulière, et le compilateur n'a pas été utile car il a été compilé sans avertissements ni erreurs.
Cela fonctionne même probablement, probablement. Je dis genre de travaux, car même si vous faites une copie d'une structure de 500 octets pour chaque itération de boucle, afin que vous puissiez inspecter une seule valeur dessus, le code est toujours complètement fonctionnel. Donc, même vos tests unitaires ne vous aident pas à réaliser qu'un mauvais code se cache derrière cette auto simple et innocente. La plupart des autres personnes qui parcourent le fichier ne le remarqueront pas non plus à première vue.
Cela peut également être aggravé si vous ne savez pas quel est le type, mais vous choisissez un nom de variable qui fait une hypothèse erronée sur ce qu'il est, obtenant en fait le même résultat qu'en 1a, mais dès le début plutôt que post-refactor.
Il me semble évident qu'auto a été introduit principalement comme solution de contournement pour une syntaxe épouvantable avec les types de modèles de bibliothèque standard. Plutôt que d'essayer de corriger la syntaxe du modèle que les gens connaissent déjà - ce qui peut également être presque impossible à faire en raison de tout le code existant qu'il pourrait casser - ajoutez un mot-clé qui cache essentiellement le problème. Essentiellement ce que vous pourriez appeler un "hack".
En fait, je n'ai aucun désaccord avec l'utilisation de l'automobile avec les conteneurs de bibliothèque standard. C'est évidemment pour cela que le mot-clé a été créé, et les fonctions de la bibliothèque standard ne sont pas susceptibles de changer fondamentalement de but (ou de type d'ailleurs), ce qui rend l'utilisation de l'automobile relativement sûre. Mais je serais très prudent à l'idée de l'utiliser avec votre propre code et vos interfaces qui peuvent être beaucoup plus volatiles et potentiellement sujettes à des changements plus fondamentaux.
Une autre application utile de l'automobile qui améliore la capacité du langage est la création de temporaires dans des macros de type agnostique. C'est quelque chose que vous ne pouviez pas vraiment faire auparavant, mais vous pouvez le faire maintenant.
Oui, il est plus facile de connaître le type de votre variable si vous n'utilisez pas auto
. La question est: avez-vous besoin de connaître le type de votre variable pour lire le code? Parfois, la réponse sera oui, parfois non. Par exemple, lorsque vous obtenez un itérateur à partir d'un std::vector<int>
, avez-vous besoin de savoir que c'est un std::vector<int>::iterator
ou serait auto iterator = ...;
suffit? Tout ce que quiconque voudrait faire avec un itérateur est donné par le fait qu'il s'agit d'un itérateur - peu importe le type spécifique.
Utilisez auto
dans les situations où cela ne rend pas votre code plus difficile à lire.
Personnellement, j'utilise auto
uniquement quand c'est absolument évident pour le programmeur de quoi il s'agit.
Exemple 1
std::map <KeyClass, ValueClass> m;
// ...
auto I = m.find (something); // OK, find returns an iterator, everyone knows that
Exemple 2
MyClass myObj;
auto ret = myObj.FindRecord (something)// NOT OK, everyone needs to go and check what FindRecord returns
Cette question sollicite l'opinion, qui variera d'un programmeur à l'autre, mais je dirais non. En fait, dans de nombreux cas, tout le contraire, auto
peut aider à rendre le code plus facile à comprendre en permettant au programmeur de se concentrer sur la logique plutôt que sur les minuties.
Cela est particulièrement vrai face aux types de modèles complexes. Voici un exemple simplifié et artificiel. Qu'est-ce qui est plus facile à comprendre?
for( std::map<std::pair<Foo,Bar>, std::pair<Baz, Bot>, std::less<BazBot>>::const_iterator it = things_.begin(); it != things_.end(); ++it )
.. ou...
for( auto it = things_.begin(); it != things_.end(); ++it )
Certains diront que le second est plus facile à comprendre, d'autres peuvent dire le premier. D'autres encore pourraient dire qu'une utilisation gratuite de auto
peut contribuer à une réduction des programmeurs qui l'utilisent, mais c'est une autre histoire.
Beaucoup de bonnes réponses jusqu'à présent, mais pour me concentrer sur la question d'origine, je pense que Herb va trop loin dans ses conseils pour utiliser auto
généreusement. Votre exemple est un cas où l'utilisation de auto
nuit évidemment à la lisibilité. Certaines personnes insistent sur le fait que ce n'est pas un problème avec les IDE modernes où vous pouvez survoler une variable et voir le type, mais je ne suis pas d'accord: même les personnes qui utilisent toujours un IDE ont parfois besoin de regarder des extraits) de code de manière isolée (pensez aux revues de code, par exemple) et un IDE n'aidera pas.
Conclusion: utilisez auto
quand cela aide: c'est-à-dire les itérateurs dans les boucles. Ne l'utilisez pas quand cela fait que le lecteur a du mal à trouver le type.
Je suis assez surpris que personne n'ait encore souligné que l'auto aide s'il n'y a pas de type clair. Dans ce cas, vous pouvez contourner ce problème en utilisant un #define ou un typedef dans un modèle pour trouver le type réellement utilisable (et ce n'est parfois pas trivial), ou vous utilisez simplement auto.
Supposons que vous ayez une fonction qui renvoie quelque chose avec un type spécifique à la plate-forme:
#ifdef PLATFROM1
__int256 getStuff();
#else //PLATFORM2
__int128 getStuff();
#endif
Quelle utilisation préférez-vous?
#ifdef PLATFORM1
__int256 stuff = getStuff();
#else
__int128 stuff = getStuff();
#endif
ou tout simplement
auto stuff = getStuff();
Bien sûr, vous pouvez écrire
#define StuffType (...)
aussi bien quelque part, mais
StuffType stuff = getStuff();
en fait en dire plus sur le type de x? Il indique que c'est ce qui est retourné à partir de là, mais c'est exactement ce qu'est auto. C'est juste redondant - 'stuff' est écrit 3 fois ici - cela à mon avis le rend moins lisible que la version 'auto'.
La lisibilité est subjective; vous devrez examiner la situation et décider de la meilleure solution.
Comme vous l'avez souligné, sans auto, les longues déclarations peuvent produire beaucoup d'encombrement. Mais comme vous l'avez également souligné, de courtes déclarations peuvent supprimer des informations de type qui peuvent être utiles.
En plus de cela, j'ajouterais également ceci: assurez-vous que vous recherchez la lisibilité et non l'écriture. Un code facile à écrire n'est généralement pas facile à lire et vice versa. Par exemple, si j'écrivais, je préférerais auto. Si je lisais, peut-être les déclarations plus longues.
Il y a ensuite la cohérence; est-ce important pour vous? Souhaitez-vous l'auto dans certaines parties et des déclarations explicites dans d'autres, ou une méthode cohérente tout au long?
Je prendrai l'avantage d'un code moins lisible et encouragerai le programmeur à l'utiliser de plus en plus. Pourquoi? De toute évidence, si le code utilisant auto est difficile à lire, il sera également difficile à écrire. Le programmeur est obligé d'utiliser le nom de variable significatif, pour améliorer son travail.
Au début, le programmeur peut ne pas écrire les noms de variables significatifs. Mais finalement, lors de la correction des bogues, ou lors de la révision du code, quand il/elle doit expliquer le code à d'autres, ou dans un avenir pas si proche, qu'il/elle explique le code aux personnes chargées de la maintenance, le programmeur réalisera l'erreur et utilisera le nom de la variable significative à l'avenir.
J'ai deux directives:
Si le type de la variable est évident, fastidieux à écrire ou difficile à déterminer, utilisez auto.
auto range = 10.0f; // Obvious
for (auto i = collection.cbegin(); i != cbegin(); ++i) // Tedious if collection type
// is really long
template <typename T> ... T t; auto result = t.get(); // Hard to determine as get()
// might return various stuff
Si vous avez besoin d'une conversion spécifique ou si le type de résultat n'est pas évident et peut provoquer de la confusion.
class B : A {}; A* foo = new B(); // 'Convert'
class Factory { public: int foo(); float bar(); }; int f = foo(); // Not obvious
Oui. Il diminue la verbosité mais le malentendu commun est que la verbosité diminue la lisibilité. Cela n'est vrai que si vous considérez la lisibilité comme esthétique plutôt que votre capacité réelle à interpréter du code - qui n'est pas augmentée en utilisant auto. Dans l'exemple le plus souvent cité, les itérateurs vectoriels, il peut apparaître à la surface que l'utilisation de l'auto augmente la lisibilité de votre code. D'un autre côté, vous ne savez pas toujours ce que le mot-clé auto vous donnera. Vous devez suivre le même chemin logique que le compilateur pour effectuer cette reconstruction interne, et la plupart du temps, en particulier avec les itérateurs, vous allez faire des hypothèses erronées.
À la fin de la journée, `` auto '' sacrifie la lisibilité du code et la clarté, pour la `` propreté '' syntaxique et esthétique (qui n'est nécessaire que parce que les itérateurs ont une syntaxe alambiquée inutilement) et la possibilité de taper peut-être 10 caractères de moins sur une ligne donnée. Cela ne vaut pas le risque ou l'effort à long terme.