Quels sont les problèmes liés à l'introduction de const de type C ++ dans un langage?

17

Je suis intéressé par l'idée de C ++ - comme constpas cette exécution particulière (comme le rejet const).

Prenez par exemple C # - il manque de const comme C ++, et la raison en est l'habituel - les gens et le temps. Ici aussi, il semble que l'équipe C # ait examiné l'exécution C ++ de constCLR marketing et en avait assez à ce stade (voir pourquoi il n'y a pas de méthode membre const dans c # et paramètre const ; merci Svick). Si nous allons plus loin, est-ce autre chose?

Y a-t-il quelque chose de plus profond? Prenons par exemple le multi-héritage - il est généralement considéré comme difficile à saisir (pour l'utilisateur) et donc non ajouté à la langue (comme le problème du diamant).

Y at - il quelque chose dans NATURE de constcette pose un problème qu'il est préférable de la langue pour l' éviter? Quelque chose comme décider si elle constdoit être profonde ou peu profonde (avoir un constconteneur signifie que je ne peux pas ajouter de nouveaux éléments ou modifier les éléments existants; que faire si les éléments sont de type référence)?

MISE À JOUR : alors que je mentionne C #, et faisant ainsi une perspective historique, je m'intéresse à la nature des problèmes potentiels constdu langage.

Ce n'est pas un défi des langues. Veuillez ignorer des facteurs tels que les tendances actuelles ou la popularité - je ne m'intéresse qu'aux problèmes techniques - merci.

greenoldman
la source
3
en aparté: l'héritage multiple n'est peut-être pas difficile à saisir, mais la résolution de la méthode peut devenir assez laide. La résolution naïve en profondeur d'abord devient rapidement peu intuitive (« problème de diamant »). L' ordre de résolution de la méthode C3 (publié en 1996) résout ce problème, mais n'est certainement pas facile à comprendre. L'interdiction de l'héritage multiple peut donc souvent sembler assez raisonnable - mais le vrai problème est que Java est antérieur à l'algorithme C3 et C # orienté lui-même après Java.
amon
3
@amon Beaucoup de gens ne pensent pas vraiment que l'héritage multiple est une fonctionnalité que vous voulez avoir en premier lieu, par exemple les créateurs du langage de programmation D: c'est une fonctionnalité complexe de valeur discutable. Il est très difficile à implémenter de manière efficace et les compilateurs sont sujets à de nombreux bogues lors de sa mise en œuvre. Presque toute la valeur de MI peut être gérée avec un héritage unique couplé à des interfaces et à une agrégation. Ce qui reste ne justifie pas le poids de la mise en œuvre de l'IM. (source)
jcora
5
Duplicate de stackoverflow.com/q/4150478/41071 .
svick
2
Même sans héritage multiple, vous pouvez déjà coder tout problème 3-SAT en tant que résolution de méthode (ou plus précisément résolution de surcharge) en C #, ce qui le rend NP-complet.
Jörg W Mittag
1
@svick, merci beaucoup. Comme je n'avais pas de réponse à cette question, j'ai pris la liberté de la modifier fortement, pour me concentrer sur la nature du problème, pas sur les raisons historiques, car il semble que 99% d'entre eux étaient "temps + personnes + biais anti C ++" + CLR ".
greenoldman

Réponses:

10

Notez bien que cela vous convient, mais dans les langages fonctionnels comme Standard ML, tout est immuable par défaut. La mutation est prise en charge via un reftype d'érence générique . Ainsi, une intvariable est immuable et une ref intvariable est un conteneur mutable pour ints. Fondamentalement, les variables sont de vraies variables au sens mathématique (une valeur inconnue mais fixe) et refs sont des "variables" au sens impératif de la programmation - une cellule mémoire qui peut être écrite et lue. (J'aime les appeler assignables .)

Je pense que le problème constest double. Tout d'abord, C ++ manque de garbage collection, ce qui est nécessaire pour avoir des structures de données persistantes non triviales . const doit être profond pour avoir un sens, mais avoir des valeurs totalement immuables en C ++ n'est pas pratique.

Deuxièmement, en C ++, vous devez vous inscrire constplutôt que de vous en retirer. Mais lorsque vous oubliez constquelque chose et que vous le corrigez plus tard, vous vous retrouverez dans la situation «empoisonnement const» mentionnée dans la réponse de @ RobY où le constchangement se répercutera en cascade dans le code. Si constc'était la valeur par défaut, vous ne vous retrouveriez pas à appliquer constrétroactivement. De plus, devoir ajouter constpartout ajoute beaucoup de bruit au code.

Je soupçonne que les langages traditionnels qui ont suivi (par exemple Java) ont été fortement influencés par le succès et la façon de penser de C et C ++. Exemple: même avec la récupération de place, la plupart des API de collecte de langues supposent des structures de données mutables. Le fait que tout soit mutable et immuabilité est considéré comme un cas d'angle en dit long sur l'état d'esprit impératif derrière les langues populaires.

EDIT : Après réflexion sur commentaire de greenoldman, je me suis rendu compte qu'il constne s'agissait pas directement de l'immuabilité des données; constencode dans le type de la méthode si elle a des effets secondaires sur l'instance.

Il est possible d'utiliser la mutation pour obtenir un comportement référentiellement transparent . Supposons que vous ayez une fonction qui, lorsqu'elle est appelée, renvoie successivement différentes valeurs - par exemple, une fonction qui lit un seul caractère stdin. Nous pourrions utiliser le cache / mémoriser les résultats de cette fonction pour produire un flux de valeurs référentiellement transparent. Le flux serait une liste chaînée dont les nœuds appellent la fonction la première fois que vous essayez de récupérer leur valeur, mais mettent ensuite en cache le résultat. Donc, si stdinconstains Hello, world!, la première fois que vous essayez de récupérer la valeur du premier nœud, il en lira unchar et reviendra H. Ensuite, il continuera à revenir Hsans autres appels pour lire a char. De même, le second noeud lisait un chardestdinla première fois que vous essayez de récupérer sa valeur, cette fois-ci en retournant eet en mettant en cache ce résultat.

La chose intéressante ici est que vous avez transformé un processus intrinsèquement dynamique en un objet apparemment apatride. Cependant, il était nécessaire de muter l'état interne de l'objet (en mettant en cache les résultats) pour y parvenir - la mutation était un effet bénin . Il est impossible de faire notre CharStream constmême si le flux se comporte comme une valeur immuable. Imaginez maintenant qu'il existe une Streaminterface avec des constméthodes, et que toutes vos fonctions s'y attendent const Streams. Votre CharStreamne peut pas implémenter l'interface!

( EDIT 2: Apparemment, il y a un mot-clé C ++ appelé mutablequi nous permettrait de tricher et de faireCharStream const Cependant, cette lacune Détruit. constLes garanties s de - maintenant vous ne pouvez vraiment pas être sûr que quelque chose ne va pas muter par ses constméthodes que je suppose que ce n'est pas. mauvais car vous devez explicitement demander l'échappatoire, mais vous êtes toujours complètement dépendant du système d'honneur.)

Deuxièmement, supposons que vous ayez des fonctions d'ordre élevé, c'est-à-dire que vous pouvez passer des fonctions comme arguments à d'autres fonctions. constness fait partie de la signature d'une fonction, vous ne pourrez donc pas passer des non- constfonctions comme arguments aux fonctions qui attendentconst fonctions. Une application aveugle constentraînerait une perte de généralité.

Enfin, la manipulation d'un const objet ne garantit pas qu'il ne mute pas un état externe (statique ou global) derrière votre dos, donc constles garanties ne sont pas aussi fortes qu'elles le paraissent initialement.

Il n'est pas clair pour moi qu'encoder la présence ou l'absence d'effets secondaires dans le système de type est universellement une bonne chose.

Doval
la source
1
+1 merci. Les objets immuables sont autre chose que C ++ const (c'est plus une vue). Mais disons, C ++ aurait const par défaut, et varà la demande ne laisserait qu'un seul problème - la profondeur?
greenoldman
1
@greenoldman Bon point. Cela fait un moment que je n'ai pas utilisé C ++, j'ai donc oublié que vous pouvez avoir une constréférence à un non- constobjet. Même ainsi, je ne pense pas qu'il soit logique d'avoir un peu profond const. Tout l'intérêt constest de garantir que vous ne pourrez pas modifier l'objet via cette référence. Vous n'êtes pas autorisé à contourner consten créant une non- constréférence, alors pourquoi serait-il acceptable de contourner consten mutant les membres de l'objet?
Doval
+1 :-) Problèmes très intéressants dans votre mise à jour (vivre avec des lambdas non / const et peut-être un problème plus faible avec l'état externe), je dois le digérer plus longtemps. Quoi qu'il en soit, en ce qui concerne votre commentaire, je n'ai rien de mal à l'esprit - bien au contraire, je recherche des fonctionnalités afin d'augmenter la clarté du code (une autre: des pointeurs non nuls). Même catégorie ici (au moins c'est mon but) - Je définis func foo(const String &s)et c'est un signal squi ne sera pas modifié foo.
greenoldman
1
@greenoldman Je peux certainement voir l'attrait et la justification. Cependant, je ne pense pas qu'il y ait une vraie solution au problème autre que d'éviter autant que possible l'état mutable (avec d'autres choses désagréables comme l' nullhéritage.)
Doval
8

Le problème principal est que les programmeurs ont tendance à ne pas l'utiliser suffisamment, donc quand ils atteignent un endroit où cela est nécessaire, ou qu'un responsable souhaite plus tard corriger la const-correct, il y a un énorme effet d'entraînement. Vous pouvez toujours écrire du code immuable parfaitement bon sans const, votre compilateur ne le fera pas appliquer et aura plus de mal à l'optimiser. Certaines personnes préfèrent que leur compilateur ne les aide pas. Je ne comprends pas ces gens, mais il y en a beaucoup.

Dans les langages fonctionnels, à peu près tout est const, et être mutable est la rare exception, si elle est autorisée. Cela présente plusieurs avantages, tels qu'une concurrence plus facile et un raisonnement plus facile sur l'état partagé, mais cela prend un certain temps pour s'y habituer si vous venez d'une langue avec une culture mutable. En d'autres termes, ne pas vouloir constest une question de personnes, pas une question technique.

Karl Bielefeldt
la source
+1 merci. Bien qu'un objet immuable soit autre chose que je recherche (vous pouvez rendre un objet immuable en C # ou Java), même avec une approche immuable, vous devez décider s'il est profond ou peu profond, prenez par exemple le conteneur de pointeurs / références (pouvez-vous changer la valeur des objets pointés). Après une journée, il semble que le problème majeur soit plutôt celui qui est défini par défaut, et il est facilement résolu lors de la conception d'un nouveau langage.
greenoldman
1
+1 pour le lolz @ "Certaines personnes préfèrent que leur compilateur ne les aide pas. Je ne comprends pas ces personnes, mais il y en a beaucoup."
Thomas Eding
6

Je vois les inconvénients comme:

  • "empoisonnement const" est un problème lorsqu'une déclaration de const peut forcer une déclaration précédente ou suivante à utiliser const, et cela peut faire en sorte que ses déclarations précédentes ou suivantes utilisent const, etc. Et si l'empoisonnement const circule dans une classe d'une bibliothèque que vous n'avez pas de contrôle, alors vous pouvez rester coincé dans un mauvais endroit.

  • ce n'est pas une garantie absolue. Il y a généralement des cas où le const protège uniquement la référence, mais est incapable de protéger les valeurs sous-jacentes pour une raison ou une autre. Même en C, il y a des cas limites auxquels const ne peut pas accéder, ce qui affaiblit la promesse que const fournit.

  • en général, le sentiment de l'industrie semble s'éloigner des garanties de compilation solides, quel que soit le réconfort qu'elles peuvent vous apporter, moi et moi. guy a sorti 10 modules Python parfaitement fonctionnels, bien conçus, bien testés et entièrement fonctionnels. Et il ne piratait même pas quand il l'a fait.

Alors peut-être que la question est de savoir pourquoi poursuivre une fonctionnalité potentiellement controversée sur laquelle même certains experts sont ambivalents lorsque vous essayez de faire adopter votre nouveau langage.

EDIT: réponse courte, une nouvelle langue a une pente raide à gravir pour adoption. Ses concepteurs doivent vraiment réfléchir aux fonctionnalités dont il a besoin pour être adoptés. Prenez Go, par exemple. Google a en quelque sorte brutalement tronqué certaines des batailles religieuses les plus profondes en faisant des choix de conception de style Conan-le-Barbare, et je pense en fait que le langage est meilleur pour cela, d'après ce que j'ai vu.

Rob
la source
2
Votre deuxième puce n'est pas correcte. En C ++, il existe trois / quatre façons de déclarer une référence / un pointeur, wrt. constness: int *pointeur mutable sur valeur int const *mutable, pointeur mutable sur valeur int * constconst, pointeur const sur valeur mutable, int const * constpointeur const sur valeur const.
phresnel
Je vous remercie. J'ai posé des questions sur la nature, pas sur le marketing ("l'industrie s'éloigne" n'est pas une nature de type C ++ const), donc de telles "raisons" que je considère non pertinentes (pour cette question au moins). À propos de l' constempoisonnement - pouvez-vous donner un exemple? Parce qu'AFAIK c'est l'avantage, tu passes quelque chose constet ça doit rester const.
greenoldman
1
Ce n'est pas le constproblème mais vraiment changer le type - quoi que vous fassiez, ce sera toujours un problème. Pensez à passer RichString, puis réalisez que vous feriez mieux de passer String.
greenoldman
4
@RobY: Je ne sais pas à quoi / à qui vous vous adressez avec ce dernier et cet ancien . Cependant: Après cet après-midi en parcourant le code, vous devriez avoir appris que vous devez refactoriser la const-correctité de bas en haut, pas de haut en bas, c'est-à-dire commencer dans les unités les plus intérieures, et remonter. Peut-être que vos unités n'étaient pas non plus suffisamment isolées, au lieu d'être étroitement couplées, ou vous êtes tombé en proie aux systèmes internes.
phresnel
7
L '«empoisonnement par const» n'est pas vraiment un problème - le vrai problème est que C ++ est par défaut non const. Il est trop facile de ne pas faire constce qui devrait être constparce que vous devez y adhérer au lieu d'en sortir.
Doval
0

Il y a quelques problèmes philosophiques à ajouter constà une langue. Si vous déclarez constà une classe, cela signifie que la classe ne peut pas changer. Mais une classe est un ensemble de variables couplées à des méthodes (ou mutateurs). Nous obtenons l'abstraction en cachant l'état d'une classe derrière un ensemble de méthodes. Si une classe est conçue pour cacher l'état, alors vous avez besoin de mutateurs pour changer cet état de l'extérieur et ce n'est constplus le cas. Il n'est donc possible de déclarer constque des classes immuables. Donc , constsemble possible que dans des scénarios où les classes (ou les types que vous voulez déclarer const) sont insignifiants ...

Alors avons-nous besoin de consttoute façon? Je ne pense pas. Je pense que vous pouvez facilement vous en passer constsi vous avez un bon design. De quelles données avez-vous besoin et où, quelles méthodes sont des méthodes de données à données et quelles abstractions avez-vous besoin pour les E / S, le réseau, la vue, etc. Ensuite, vous divisez naturellement les modules et les classes qui traitent de l'état et vous avez des méthodes et classes immuables qui n'ont pas besoin d'état.

Je pense que la plupart des problèmes surviennent avec de gros objets de données volumineux qui peuvent se sauvegarder, se transformer et se dessiner et ensuite vous voulez le passer à un moteur de rendu par exemple. Vous ne pouvez donc pas copier le contenu des données, car c'est trop cher pour un gros objet de données. Mais vous ne voulez pas non plus que cela change, vous avez donc besoin de quelque chose comme constvous le pensez. Laissez le compilateur découvrir vos défauts de conception. Cependant, si vous divisez ces objets en un seul objet de données immuable et implémentez les méthodes dans le module responsable, vous pouvez passer (uniquement) le pointeur et faire les choses que vous voulez faire avec les données tout en garantissant l'immuabilité.

Je sais qu'il est possible en C ++ de déclarer certaines méthodes constet de ne pas déclarer sur d'autres méthodes, car ce sont des mutateurs. Et puis quelque temps plus tard, vous avez besoin d'un cache, puis un getter mute un objet (car il se charge paresseusement) et ce n'est constplus le cas. Mais c'était tout l'intérêt de cette abstraction, n'est-ce pas? Vous ne savez pas quel état est muté à chaque appel.

Waster
la source
1
"Donc, const ne semble possible que dans les scénarios où les classes (ou les types que vous voulez déclarer const) sont triviales ..." Les structures de données persistantes sont immuables et non triviales. Vous ne les verrez tout simplement pas en C ++, car presque tous partagent leurs données internes entre plusieurs instances, et C ++ manque de récupération de place pour que cela fonctionne. "Je pense que vous pouvez facilement vous passer de const si vous avez un bon design." Les valeurs immuables rendent le code beaucoup plus simple à analyser.
Doval
+1 merci. Pourriez-vous clarifier "déclarer const à une classe"? Voulez-vous dire définir la classe en tant que const? Si oui, je ne pose pas de question à ce sujet - C ++ const fonctionne comme une vue, vous pouvez déclarer un const à côté de l'objet, pas une classe (ou quelque chose changé en C ++ 11). Il y a aussi un problème avec le paragraphe suivant et le mot "facilement" - il y a une bonne question sur C ++ - const en C # et la quantité de travail qu'il implique est primordiale - pour chaque type que vous avez l'intention de mettre const(dans le sens C ++ ) vous devez définir l'interface, également pour toutes les propriétés.
greenoldman
1
Quand vous parlez d'une "classe", vous voulez dire "instance d'une classe", n'est-ce pas? Je pense que c'est une distinction importante.
svick