Il semble que auto
c'était une fonctionnalité assez importante à ajouter dans C ++ 11 qui semble suivre un grand nombre des nouveaux langages. Comme avec un langage comme Python, je n'ai vu aucune déclaration de variable explicite (je ne sais pas si c'est possible en utilisant les standards Python).
Y a-t-il un inconvénient à utiliser auto
pour déclarer des variables au lieu de les déclarer explicitement?
c++
c++11
type-inference
auto
DxAlpha
la source
la source
Réponses:
Vous avez seulement posé des questions sur les inconvénients, alors j'en souligne certains. Lorsqu'il est bien utilisé, il
auto
présente également plusieurs avantages. Les inconvénients résultent de la facilité des abus et du potentiel accru du code à se comporter de manière non intentionnelle.Le principal inconvénient est qu'en utilisant
auto
, vous ne connaissez pas nécessairement le type d'objet en cours de création. Il y a aussi des occasions où le programmeur peut s'attendre à ce que le compilateur déduise un type, mais le compilateur en déduit catégoriquement un autre.Étant donné une déclaration comme
vous ne savez pas forcément quel type
result
s'agit. Cela pourrait être unint
. Cela pourrait être un indicateur. Cela pourrait être autre chose. Tous ces éléments prennent en charge différentes opérations. Vous pouvez également modifier considérablement le code par un changement mineur commecar, en fonction des surcharges existantes,
CallSomeFunction()
le type de résultat peut être complètement différent - et le code suivant peut donc se comporter complètement différemment de ce qui est prévu. Vous pourriez soudainement déclencher des messages d'erreur dans le code ultérieur (par exemple, essayer de déréférencer unint
, essayer de changer quelque chose qui est maintenantconst
). Le changement le plus sinistre est celui où votre changement passe devant le compilateur, mais le code suivant se comporte de manière différente et inconnue - peut-être boguée.Ne pas avoir de connaissance explicite du type de certaines variables rend donc plus difficile de justifier rigoureusement une affirmation selon laquelle le code fonctionne comme prévu. Cela signifie plus d'efforts pour justifier les allégations de «convenance à l'usage» dans les domaines de haute criticité (par exemple, critiques pour la sécurité ou la mission).
L'autre inconvénient, plus courant, est la tentation pour un programmeur d'utiliser
auto
comme un instrument brutal pour forcer le code à se compiler, plutôt que de penser à ce que le code fait et de travailler pour le faire correctement.la source
auto
, alors la plupart des langages typés canard souffrent d'un tel inconvénient par conception!CallSomeFunction()
retourne un type différent selon la séquence de ses arguments, c'est un défaut de conception deCallSomeFunction()
, pas un problème deauto
. Si vous ne lisez pas la documentation d'une fonction que vous utilisez avant de l'utiliser, c'est un défaut du programmeur, pas un problème deauto
. - Mais je comprends que vous jouez l'avocat du diable ici, c'est juste que Nir Friedman a le bien meilleur cas.T CallSomeFunction(T, int, int)
un défaut de conception? De toute évidence, il "renvoie un type différent en fonction de la séquence de ses arguments".auto
, vous ne connaissez pas nécessairement le type d'objet en cours de création." Pouvez-vous expliquer pourquoi c'est un problème avecauto
, et non un problème avec les sous-expressions temporaires? Pourquoi est-ceauto result = foo();
mauvais, maisfoo().bar()
pas?Ce n'est pas
auto
exactement un inconvénient de principe, mais en termes pratiques, cela semble être un problème pour certains. Fondamentalement, certaines personnes: a) se traitentauto
comme un sauveur pour les types et bloquent leur cerveau lorsqu'elles l'utilisent, ou b) oublient que celaauto
déduit toujours des types de valeur. Cela amène les gens à faire des choses comme ceci:Oups, nous venons de copier en profondeur un objet. C'est souvent un bug ou un échec de performance. Ensuite, vous pouvez également basculer dans l'autre sens:
Maintenant, vous obtenez une référence pendante. Ces problèmes ne sont pas causés par
auto
, donc je ne les considère pas comme des arguments légitimes contre cela. Mais il semble queauto
ces problèmes soient plus courants (d'après mon expérience personnelle), pour les raisons que j'ai énumérées au début.Je pense qu'avec le temps, les gens s'adapteront et comprendront la division du travail: en
auto
déduit le type sous-jacent, mais vous voulez toujours penser à la référence et à la constance. Mais cela prend un peu de temps.la source
std::vector
). Le fait d'être coûteux à copier n'est pas une propriété d'une classe, mais des objets individuels. Celamethod_that_returns_reference
pourrait donc faire référence à un objet d'une classe qui a un constructeur de copie, mais dont l'objet s'avère assez coûteux à copier (et ne peut pas être déplacé).std::vector
? (Parce que cela pourrait, oui, ou parce que vous ne contrôlez pas la classe, mais ce n'est pas le but) Si la copie coûte cher (et ne possède aucune ressource, car elle est copiable), pourquoi ne pas utiliser COW sur l'objet? La localité de données est déjà tuée par la taille de l'objet.= delete
cette surcharge. Bien que plus généralement ce que vous dites est une solution. C'est un sujet que j'ai exploré, si cela vous intéresse: nirfriedman.com/2016/01/18/… .D'autres réponses mentionnent des inconvénients comme «vous ne savez pas vraiment quel est le type d'une variable». Je dirais que cela est largement lié à une convention de dénomination bâclée dans le code. Si vos interfaces sont clairement nommées, vous ne devriez pas avoir à vous soucier du type exact. Bien sûr,
auto result = callSomeFunction(a, b);
ça ne vous dit pas grand chose. Maisauto valid = isValid(xmlFile, schema);
vous en dit assez pour l'utiliservalid
sans avoir à vous soucier de son type exact. Après tout, avec justeif (callSomeFunction(a, b))
, vous ne sauriez pas non plus le type. La même chose avec tous les autres objets temporaires de sous-expression. Je ne considère donc pas cela comme un réel inconvénientauto
.Je dirais que son principal inconvénient est que parfois, le type de retour exact n'est pas celui avec lequel vous voulez travailler. En effet, le type de retour réel diffère parfois du type de retour "logique" en tant que détail d'implémentation / d'optimisation. Les modèles d'expression en sont un excellent exemple. Disons que nous avons ceci:
Logiquement, nous nous attendrions
SomeType
à l'êtreVector
, et nous voulons absolument le traiter comme tel dans notre code. Cependant, il est possible qu'à des fins d'optimisation, la bibliothèque d'algèbre que nous utilisons implémente des modèles d'expression, et le type de retour réel est le suivant:Maintenant, le problème est que
MultExpression<Matrix, Vector>
, selon toute vraisemblance, stockera unconst Matrix&
et enconst Vector&
interne; il s'attend à ce qu'il se convertisse en aVector
avant la fin de son expression complète. Si nous avons ce code, tout va bien:Cependant, si nous avions utilisé
auto
ici, nous pourrions avoir des ennuis:la source
auto
a très peu d'inconvénients, alors je m'en tiens à cette force. Et d'autres exemples de proxies, etc. incluent divers "générateurs de chaînes" et objets similaires trouvés dans les DSL.auto
avant, en particulier avec la bibliothèque Eigen. C'est particulièrement délicat car le problème n'apparaît souvent pas dans les versions de débogage.auto
peut également mordre lors de l'utilisation de la bibliothèque de matrices Armadillo , qui utilise fortement la méta-programmation de modèles à des fins d'optimisation. Heureusement, les développeurs ont ajouté la fonction .eval () qui peut être utilisée pour éviter les problèmes avecauto
auto
impliquer généralement une sorte de vérification de type (et éclabousserauto
partout enlève cette sécurité de type comme partout ailleurs). Ce n'est pas une bonne comparaison.L'un des inconvénients est que parfois vous ne pouvez pas déclarer
const_iterator
avecauto
. Vous obtiendrez un itérateur ordinaire (non const) dans cet exemple de code tiré de cette question :la source
iterator
dans tous les cas puisque votre carte n'est pasconst
. si vous voulez le convertir en aconst_iterator
, spécifiez explicitement le type de variable comme d'habitude, ou extrayez une méthode de sorte que votre map soit const dans le contexte de votrefind
. (Je préférerais ce dernier. SRP.)auto city_it = static_cast<const auto&>(map).find("New York")
? ou, avec C ++ 17auto city_if = std::as_const(map).find("New York")
.Cela rend votre code un peu plus difficile ou fastidieux à lire. Imaginez quelque chose comme ça:
Maintenant, pour déterminer le type de sortie, vous devez rechercher la signature de la
doSomethingWithData
fonction.la source
auto it = vec.begin();
est beaucoup plus facile à lire questd::vector<std::wstring>::iterator it = vec.begin();
par exemple.Comme ce développeur, je déteste
auto
. Ou plutôt, je déteste la façon dont les gens abusentauto
.Je suis d'avis (fort) que
auto
c'est pour vous aider à écrire du code générique, pas pour réduire la frappe .C ++ est un langage dont le but est de vous permettre d'écrire du code robuste, pas de minimiser le temps de développement.
Ceci est assez évident à partir de nombreuses fonctionnalités de C ++, mais malheureusement quelques-unes des plus récentes comme
auto
celle-ci réduisent la saisie en erreur en leur faisant croire qu'elles devraient commencer à être paresseuses avec la frappe.Auparavant
auto
, les gens utilisaienttypedef
s, ce qui était génial parce que celatypedef
permettait au concepteur de la bibliothèque de vous aider à déterminer quel devrait être le type de retour, afin que leur bibliothèque fonctionne comme prévu. Lorsque vous utilisezauto
, vous enlevez ce contrôle au concepteur de la classe et demandez à la place au compilateur de déterminer quel doit être le type, ce qui supprime l'un des outils C ++ les plus puissants de la boîte à outils et risque de casser son code.En général, si vous utilisez
auto
, cela devrait être parce que votre code fonctionne pour n'importe quel type raisonnable , pas parce que vous êtes simplement trop paresseux pour écrire le type avec lequel il devrait fonctionner. Si vous utilisezauto
comme outil pour aider à la paresse, alors ce qui se passe, c'est que vous commencez finalement à introduire des bogues subtils dans votre programme, généralement causés par des conversions implicites qui ne se sont pas produites parce que vous avez utiliséauto
.Malheureusement, ces bogues sont difficiles à illustrer dans un court exemple ici parce que leur brièveté les rend moins convaincants que les exemples réels qui surviennent dans un projet utilisateur - cependant, ils se produisent facilement dans un code lourd en modèle qui s'attend à certaines conversions implicites. prennent endroit.
Si vous voulez un exemple, il y en a un ici . Une petite note cependant: avant d'être tenté de sauter et de critiquer le code: gardez à l'esprit que de nombreuses bibliothèques bien connues et matures ont été développées autour de telles conversions implicites, et elles sont là parce qu'elles résolvent des problèmes qui peuvent être difficiles voire impossibles à résoudre autrement. Essayez de trouver une meilleure solution avant de les critiquer.
la source
which was great because typedef allowed the designer of the library to help you figure out what the return type should be, so that their library works as expected. When you use auto, you take away that control from the class's designer and instead ask the compiler to figure out what the type should be
Pas vraiment une bonne raison IMO. Les IDE à jour, Visual Studio 2015 par exemple, vous permettent de vérifier le type de la variable en survolantauto
. C'est * exactement * le même que celui-typedef
là.typename std::iterator_traits<It>::value_type
. (2) Le point essentiel était que le type inféré n'était pas nécessairement "exactement le même" que le type correct prévu par le concepteur précédent du code; en utilisantauto
, vous enlevez la capacité du concepteur à spécifier le type correct.vector<bool>
non-sens" ... pardon? Comment pensez-vous qu'ilbitset
est mis en œuvre? Ou considérez-vous que les conteneurs de bits sont complètement absurdes?!auto
n'a pas d'inconvénients en soi , et je recommande de l'utiliser (à la main) partout dans le nouveau code. Il permet à votre code de vérifier systématiquement le type et d'éviter systématiquement le découpage silencieux. (SiB
dérive deA
et qu'une fonction retournantA
soudainement retourneB
, alorsauto
se comporte comme prévu pour stocker sa valeur de retour)Cependant, le code hérité pré-C ++ 11 peut s'appuyer sur des conversions implicites induites par l'utilisation de variables explicitement typées. Changer une variable explicitement typée en
auto
peut changer le comportement du code , vous feriez donc mieux d'être prudent.la source
auto
il y a des inconvénients en soi (ou du moins - beaucoup le pensent). Prenons l'exemple donné dans la deuxième question de cette table ronde avec Sutter, Alexandrescu et Meyers: si vous avezauto x = foo(); if (x) { bar(); } else { baz(); }
etfoo()
retournebool
- que se passe-t-il sifoo()
change pour renvoyer une énumération (trois options au lieu de deux)? Leauto
code continuera à fonctionner, mais produira des résultats inattendus.bool
au lieu deauto
changer quoi que ce soit en cas d'énumération sans portée? Je me trompe peut-être (je ne peux pas vérifier ici), mais je pense que la seule différence est que la conversion sebool
produit lors de la déclaration de variable au lieu de l'évaluation de la condition dans le fichierif
. Si leenum
est défini, la conversion enbool
ne doit pas avoir lieu sans préavis explicite.Le mot-clé
auto
déduit simplement le type de la valeur de retour. Par conséquent, ce n'est pas équivalent avec un objet Python, par exempleComme il
auto
est déduit lors de la compilation, il n'aura aucun inconvénient à l'exécution.la source
type()
en python. Il en déduit le type, il ne crée pas de nouvelle variable de ce type.decltype
.auto
est pour l'affectation de variables spécifiquement.Ce que personne n'a mentionné ici jusqu'à présent, mais en soi, vaut une réponse si vous me le demandez.
Depuis (même si tout le monde doit savoir que
C != C++
) le code écrit en C peut facilement être conçu pour fournir une base pour le code C ++ et donc être conçu sans trop d'effort pour être compatible C ++, cela pourrait être une exigence de conception.Je connais certaines règles dont certaines constructions bien définies ne
C
sont pas validesC++
et vice versa. Mais cela entraînerait simplement des exécutables cassés et la clause UB connue s'applique, ce qui est la plupart du temps remarqué par des boucles étranges entraînant des plantages ou autre (ou même peut rester non détecté, mais cela n'a pas d'importance ici).Mais
auto
c'est la première fois 1 ce changement!Imaginez que vous utilisiez auparavant
auto
comme spécificateur de classe de stockage et que vous transfériez le code. Il ne serait même pas nécessairement (selon la manière dont il était utilisé) "casser"; il pourrait en fait modifier silencieusement le comportement du programme.C'est quelque chose qu'il faut garder à l'esprit.
1 Au moins la première fois que je suis au courant.
la source
int
" en C, vous méritez toutes les mauvaises choses que vous en tirerez. Et si vous ne vous y fiez pas, utiliserauto
comme spécificateur de classe de stockage avec un type vous donnera une belle erreur de compilation en C ++ (ce qui est une bonne chose dans ce cas).Une raison à laquelle je peux penser est que vous perdez l'occasion de contraindre la classe qui est renvoyée. Si votre fonction ou méthode a renvoyé un long 64 bits et que vous ne vouliez qu'un 32 entier non signé, vous perdez l'opportunité de contrôler cela.
la source
Comme je l'ai décrit dans cette réponse, cela
auto
peut parfois entraîner des situations géniales auxquelles vous n'aviez pas l'intention. Vous devez dire explicitementauto&
avoir un type de référence tout en faisant simplementauto
pouvoir créer un type de pointeur. Cela peut entraîner une confusion en omettant le spécificateur tous ensemble, ce qui entraîne une copie de la référence au lieu d'une référence réelle.la source
auto
fait, sans jamais déduire de référence ni deconst
type. Pour uneauto
référence, vous feriez mieux d'utiliserauto&&
. (une référence universelle) Si le type n'est pas bon marché à copier ou possède une ressource, alors le type ne doit pas être copiable pour commencer.Un autre exemple irritant:
génère un avertissement (
comparison between signed and unsigned integer expressions [-Wsign-compare]
), cari
est un entier signé. Pour éviter cela, vous devez écrire par exempleou peut-être mieux:
la source
size
retourssize_t
, vous devez pouvoir avoir unsize_t
littéral comme0z
. Mais vous pouvez déclarer un UDL pour ce faire. (size_t operator""_z(...)
)unsigned
est fort probable que ce ne soit pas assez grand pour contenir toutes les valeurs destd::size_t
sur les architectures grand public, donc dans le cas peu probable où quelqu'un aurait un conteneur avec un nombre absurdement gigantesque d'éléments, l'utilisationunsigned
pourrait provoquer une boucle infinie sur la plage inférieure d'indices. Bien que ce ne soit probablement pas un problème, ilstd::size_t
doit être utilisé pour obtenir un code propre qui signale correctement l'intention. Je ne suis pas sûr qu'ilunsigned long long
soit strictement garanti que cela suffise, même si dans la pratique, cela doit probablement être le même.unsigned long long
est garanti au moins 64 bits, mais en théoriesize_t
pourrait être plus grand que cela, je suppose. Bien sûr, si vous avez> 2 ^ 64 éléments dans votre conteneur, vous pouvez avoir de plus gros problèmes à craindre ... ;-)Je pense que
auto
c'est bon lorsqu'il est utilisé dans un contexte localisé, où le lecteur peut facilement et évidemment déduire son type, ou bien documenté avec un commentaire de son type ou un nom qui en déduit le type réel. Ceux qui ne comprennent pas comment cela fonctionne pourraient le prendre de la mauvaise manière, comme l'utiliser à la placetemplate
ou similaire. Voici quelques bons et mauvais cas d'utilisation à mon avis.Bonnes utilisations
Itérateurs
Pointeurs de fonction
Mauvaises utilisations
Flux de données
Signature de fonction
Cas triviaux
la source
int
, vous devez taper un autre caractère si vous optez pourauto
. C'est inacceptableint
aussi facile à voir ici et la frappeint
est plus courte. C'est pourquoi c'est un cas trivial.Je suis surpris que personne n'ait mentionné cela, mais supposons que vous calculiez la factorielle de quelque chose:
Ce code affichera ceci:
Ce n'était certainement pas le résultat escompté. C'est arrivé parce que
auto
déduit le type de la variable factorielle commeint
parce qu'elle était affectée1
.la source