C ++ 17 introduit l' [[nodiscard]]
attribut, qui permet aux programmeurs de marquer les fonctions de manière à ce que le compilateur génère un avertissement si l'objet renvoyé est ignoré par un appelant. le même attribut peut être ajouté à un type de classe entier.
J'ai lu des explications sur la motivation de cette fonctionnalité dans la proposition d' origine et je sais que C ++ 20 ajoutera l'attribut à des fonctions standard telles que std::vector::empty
, dont les noms ne transmettent pas une signification non ambiguë concernant la valeur de retour.
C'est une fonctionnalité intéressante et utile. En fait, cela semble presque trop utile. Partout où je lis [[nodiscard]]
, les gens en discutent comme si vous ajoutiez simplement cela à quelques fonctions ou types et oubliez le reste. Mais pourquoi une valeur non éliminable devrait-elle constituer un cas particulier, en particulier lors de l'écriture d'un nouveau code? Une valeur de retour supprimée n'est-elle pas généralement un bogue ou du moins un gaspillage de ressources?
Et l’un des principes de conception de C ++ n’est-il pas que le compilateur doit intercepter autant d’erreurs que possible?
Si tel est le cas, pourquoi ne pas ajouter [[nodiscard]]
votre propre code, non hérité, à presque chaque void
type de classe et à chaque type de fonction?
J'ai essayé de le faire dans mon propre code, et cela fonctionne bien, sauf que c'est tellement terriblement prolixe que ça commence à ressembler à Java. Il semblerait beaucoup plus naturel de mettre les compilateurs en garde contre les valeurs renvoyées par défaut, à l' exception des rares cas où vous indiquez votre intention [*] .
Comme je n’ai vu aucune discussion sur cette possibilité dans les propositions standard, les entrées de blog, les questions Stack Overflow ou ailleurs sur Internet, il me manque quelque chose.
Pourquoi une telle mécanique n'aurait-elle pas de sens dans le nouveau code C ++? La verbosité est-elle la seule raison de ne pas utiliser [[nodiscard]]
presque partout?
[*] En théorie, vous pouvez avoir quelque chose comme un [[maydiscard]]
attribut, qui pourrait aussi être ajouté rétroactivement à des fonctions comme printf
dans les implémentations de bibliothèques standard.
operator =
par exemple. Etstd::map::insert
.const
peut enfler une autre « simple » classe (ou plutôt « bon vieux objet de données ») considérablement en C ++.std::vector
oustd::unique_ptr
dont vous avez simplement besoin de données membres dans votre définition de classe. J'ai travaillé avec les deux langues. Java est une langue okayish, mais plus verbeuse.Réponses:
Dans les nouveaux codes qui ne doivent pas nécessairement être compatibles avec les normes plus anciennes, utilisez cet attribut chaque fois que vous le souhaitez. Mais pour C ++,
[[nodiscard]]
fait un mauvais défaut. Vous suggérez:Cela provoquerait soudainement un code correct existant qui émettrait de nombreux avertissements. Bien qu'un tel changement puisse techniquement être considéré comme rétrocompatible étant donné que tout code existant est toujours compilé avec succès, ce serait un énorme changement de sémantique dans la pratique.
Les décisions de conception pour un langage mature existant avec une très grande base de code sont nécessairement différentes des décisions de conception pour un langage complètement nouveau. S'il s'agissait d'une nouvelle langue, il serait judicieux d'avertir par défaut. Par exemple, le langage Nim exige que les valeurs inutiles soient ignorées explicitement - ce qui serait similaire à l'encapsulation de chaque instruction d'expression en C ++
(void)(...)
.Un
[[nodiscard]]
attribut est le plus utile dans deux cas:si une fonction n'a pas d'effet au-delà d'un résultat donné, c'est-à-dire pure. Si le résultat n'est pas utilisé, l'appel est certainement inutile. D'autre part, ignorer le résultat ne serait pas incorrect.
si la valeur de retour doit être vérifiée, par exemple pour une interface de type C qui renvoie des codes d'erreur au lieu d'être générée. C'est le cas d'utilisation principal. Pour le C ++ idiomatique, cela va être assez rare.
Ces deux cas laissent un immense espace de fonctions impures qui retournent une valeur, où un tel avertissement serait trompeur. Par exemple:
Considérons un type de données de file d'attente avec une
.pop()
méthode qui supprime un élément et renvoie une copie de l'élément supprimé. Une telle méthode est souvent pratique. Cependant, dans certains cas, nous souhaitons uniquement supprimer l'élément sans en obtenir une copie. C'est parfaitement légitime, et un avertissement ne serait pas utile. Une conception différente (telle questd::vector
) divise ces responsabilités, mais comporte d'autres compromis.Notez que dans certains cas, une copie doit être faite de toute façon, donc, grâce à RVO, le retour de la copie serait gratuit.
Envisagez des interfaces fluides, où chaque opération renvoie l'objet afin que d'autres opérations puissent être effectuées. En C ++, l'exemple le plus courant est l'opérateur d'insertion de flux
<<
. Il serait extrêmement fastidieux d’ajouter un[[nodiscard]]
attribut à chaque<<
surcharge.Ces exemples montrent que rendre la compilation de code C ++ idiomatique sans avertissements dans un langage «C ++ 17 avec nodiscard par défaut» serait assez fastidieux.
Notez que votre tout nouveau code C ++ 17 (où vous pouvez utiliser ces attributs) peut toujours être compilé avec des bibliothèques qui ciblent des normes C ++ plus anciennes. Cette compatibilité en amont est cruciale pour l'écosystème C / C ++. Par conséquent, si vous définissez nodiscard comme valeur par défaut, de nombreux avertissements seraient utilisés dans des cas d'utilisation idiomatiques typiques, avertissements que vous ne pouvez pas corriger sans modifier de manière approfondie le code source de la bibliothèque.
On peut soutenir que le problème ici n’est pas le changement de sémantique, mais que les fonctionnalités de chaque norme C ++ s’appliquent à une étendue par unité de compilation et non à une étendue par fichier. Si / quand une future norme C ++ s'éloigne des fichiers d'en-tête, une telle modification serait plus réaliste.
la source
pop
ouoperator<<
, celles-ci seraient explicitement marquées par mon[[maydiscard]]
attribut imaginaire , ainsi aucun avertissement ne serait généré. Êtes-vous en train de dire que de telles fonctions sont généralement beaucoup plus courantes que ce que j'aimerais croire, ou beaucoup plus courantes en général que dans mes propres bases de code? Votre premier paragraphe sur le code existant est certes vrai, mais je me demande tout de même si quelqu'un d'autre utilise actuellement tout son nouveau code[[nodiscard]]
, et si ce n'est pas le cas, pourquoi pas.returns_an_int() = 0
, alors pourquoi devraitreturns_a_str() = ""
travailler? C ++ 11 a montré que c'était en fait une idée terrible, car maintenant tout ce code interdisait les déplacements, car les valeurs étaient const. Je pense que la leçon à retenir de cette leçon est "ce n'est pas à vous de voir ce que vos appelants font avec votre résultat". Ignorer le résultat est une chose tout à fait valable que les appelants pourraient vouloir faire et, à moins que vous sachiez que c'est faux (une barre très haute), vous ne devriez pas le bloquer.empty()
est également utile car son nom est assez ambigu: est-ce un prédicatis_empty()
ou un mutateurclear()
? Vous ne vous attendriez généralement pas à ce qu'un tel mutateur retourne quoi que ce soit, donc un avertissement concernant une valeur de retour peut alerter le programmeur d'un problème. Avec un nom plus clair, cet attribut ne serait pas aussi nécessaire.[[pure]]
attribut, et cela devrait impliquer[[nodiscard]]
. De cette façon, il est clair que ce résultat ne doit pas être ignoré. Mais à part les fonctions pures, je ne peux pas penser à une autre classe de fonctions qui devrait avoir ce comportement. Et comme la plupart des fonctions ne sont pas pures, je ne pense pas que nodiscard par défaut soit le bon choix.Raisons pour lesquelles je n’aurais pas
[[nodiscard]]
presque partout:Maintenant, si vous le définissiez par défaut, vous obligeriez tous les développeurs de bibliothèques à obliger tous leurs utilisateurs à ne pas ignorer les valeurs renvoyées. Ce serait horrible. Ou vous les obligez à ajouter
[[maydiscard]]
d'innombrables fonctions, ce qui est également assez horrible.la source
Par exemple: l'opérateur << a une valeur de retour qui dépend de l'appel, soit absolument nécessaire, soit absolument inutile. (std :: cout << x << y, le premier est nécessaire car il renvoie le flux, le second est inutile). Maintenant, comparons avec printf où tout le monde rejette la valeur de retour qui est là pour la vérification d'erreur, mais l'opérateur << n'a pas d'erreur de vérification et n'a donc pas pu être aussi utile en premier lieu. Donc, dans les deux cas, Nodiscard serait juste dommageable.
la source
[[nodiscard]]
partout?".