web-dev-qa-db-fra.com

Quelle est la raison de ne pas utiliser [[nodiscard]] de C ++ 17 presque partout dans le nouveau code?

C++ 17 introduit le [[nodiscard]] attribut, qui permet aux programmeurs de marquer les fonctions de manière à ce que le compilateur produise un avertissement si l'objet renvoyé est rejeté par un appelant; le même attribut peut être ajouté à un type de classe entier.

J'ai lu la motivation de cette fonctionnalité dans l'original proposition , et je sais que C++ 20 ajoutera l'attribut aux fonctions standard comme std::vector::empty , dont les noms ne véhiculent pas une signification sans ambiguïté concernant la valeur de retour.

C'est une fonctionnalité intéressante et utile. En fait, cela semble presque aussi utile. Partout où je lis sur [[nodiscard]], les gens en discutent comme si vous les ajoutiez à quelques fonctions ou types et oubliez le reste. Mais pourquoi une valeur non jetable devrait-elle être un cas spécial, en particulier lors de l'écriture de nouveau code? Une valeur de retour rejetée n'est-elle généralement pas un bogue ou au moins un gaspillage de ressources?

Et n'est-ce pas l'un des principes de conception du C++ lui-même que le compilateur doit détecter autant d'erreurs que possible?

Si oui, alors pourquoi ne pas ajouter [[nodiscard]] dans votre propre code non hérité vers presque toutes les fonctions nonvoid et presque chaque type de classe?

J'ai essayé de le faire dans mon propre code, et cela fonctionne bien, sauf qu'il est si terriblement verbeux qu'il commence à ressembler à Java. Il semblerait beaucoup plus naturel de faire avertir les compilateurs des valeurs de retour rejetées par défaut sauf pour les quelques autres cas où vous marquez votre intention[*].

Comme je n'ai vu aucune discussion sur cette possibilité dans les propositions standard, les entrées de blog, les questions de débordement de pile ou n'importe où ailleurs sur Internet, je dois manquer 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 à la place, qui pourrait également être ajouté rétroactivement à des fonctions comme printf dans les implémentations de bibliothèque standard.

81
Christian Hackl

Dans le nouveau code qui n'a pas besoin d'être compatible avec les normes plus anciennes, utilisez cet attribut partout où cela est judicieux. Mais pour C++, [[nodiscard]] Fait un mauvais défaut. Vous suggérez:

Il semblerait beaucoup plus naturel de faire avertir les compilateurs des valeurs de retour ignorées par défaut, sauf dans les quelques autres cas où vous marquez votre intention.

Cela provoquerait soudainement un code correct existant pour émettre de nombreux avertissements. Bien qu'un tel changement puisse techniquement être considéré comme rétrocompatible puisque tout code existant se compile toujours avec succès, ce serait un énorme changement de sémantique dans la pratique.

Les décisions de conception pour un langage existant et mature avec une très grande base de code sont nécessairement différentes des décisions de conception pour un tout nouveau langage. S'il s'agissait d'une nouvelle langue, l'avertissement par défaut serait judicieux. Par exemple, le langage Nim requiert que les valeurs inutiles soient éliminées explicitement - cela reviendrait à encapsuler chaque instruction d'expression en C++ avec un transtypage (void)(...).

Un attribut [[nodiscard]] Est plus utile dans deux cas:

  • si une fonction n'a aucun effet au-delà du retour d'un certain résultat, c'est-à-dire qu'elle est pure. Si le résultat n'est pas utilisé, l'appel est certainement inutile. En revanche, la suppression du résultat ne serait pas incorrecte.

  • si la valeur de retour doit être vérifiée, par ex. pour une interface de type C qui renvoie des codes d'erreur au lieu de lancer. Il s'agit du cas d'utilisation principal. Pour le C++ idiomatique, cela va être assez rare.

Ces deux cas laissent un immense espace de fonctions impures qui renvoient une valeur, où un tel avertissement serait trompeur. Par exemple:

  • Considérez un type de données de file d'attente avec une méthode .pop() qui supprime un élément et retourne une copie de l'élément supprimé. Une telle méthode est souvent pratique. Cependant, dans certains cas, nous voulons uniquement supprimer l'élément, sans en obtenir de copie. C'est parfaitement légitime et un avertissement ne serait pas utile. Une conception différente (telle que std::vector) Divise ces responsabilités, mais cela a 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.

  • Considérez les 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 lourd d'ajouter un attribut [[nodiscard]] À chaque surcharge de <<.

Ces exemples montrent que la compilation de code C++ idiomatique sans avertissements sous un langage "C++ 17 avec nodiscard par défaut" serait assez fastidieuse.

Notez que votre nouveau code C++ 17 brillant (où vous pouvez utiliser ces attributs) peut toujours être compilé avec des bibliothèques qui ciblent les anciennes normes C++. Cette rétrocompatibilité est cruciale pour l'écosystème C/C++. Ainsi, faire de nodiscard la valeur par défaut entraînerait de nombreux avertissements pour des cas d'utilisation typiques et idiomatiques - des avertissements que vous ne pouvez pas corriger sans apporter des modifications profondes au code source de la bibliothèque.

On peut dire que le problème ici n'est pas le changement de sémantique mais que les fonctionnalités de chaque norme C++ s'appliquent sur une portée par unité de compilation et non sur une portée par fichier. Si/quand une future norme C++ s'éloigne des fichiers d'en-tête, un tel changement serait plus réaliste.

81
amon

Raisons pour lesquelles je ne serais pas [[nodiscard]] presque partout:

  1. (majeur :) Cela introduirait façon trop de bruit dans mes en-têtes.
  2. (majeur :) Je ne pense pas que je devrais faire de fortes hypothèses spéculatives sur le code des autres. Vous voulez supprimer la valeur de retour que je vous donne? Très bien, assommez-vous.
  3. (mineur :) Vous garantissez une incompatibilité avec C++ 14

Maintenant, si vous le définissiez par défaut, vous forceriez tous les développeurs de bibliothèques à forcer tous leurs utilisateurs à ne pas ignorer les valeurs de retour. Ce serait horrible. Ou vous les forcez à ajouter [[maydiscard]] dans d'innombrables fonctions, ce qui est aussi horrible.

11
einpoklum

Par exemple: operator << 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 ne sert à rien). Maintenant, comparez avec printf où tout le monde rejette la valeur de retour qui est là pour la vérification des erreurs, mais l'opérateur << n'a pas de vérification d'erreur, donc cela n'a pas pu être si utile en premier lieu. Donc, dans les deux cas, la nodiscard serait tout simplement dommageable.

0
gnasher729