353 votes

Pourquoi le C++ ne dispose-t-il pas de la réflexion ?

Il s'agit d'une question quelque peu étrange. Mes objectifs sont de comprendre la décision de conception du langage et d'identifier les possibilités de réflexion en C++.

  1. Pourquoi le comité de langue C++ n'a-t-il pas cherché à intégrer la réflexion dans la langue ? La réflexion est-elle trop difficile dans un langage qui ne fonctionne pas sur une machine virtuelle (comme Java) ?

  2. Si l'on devait mettre en œuvre la réflexion en C++, quels seraient les défis à relever ?

Je suppose que les utilisations de la réflexion sont bien connues : les éditeurs peuvent être écrits plus facilement, le code du programme sera plus petit, des mocks peuvent être générés pour les tests unitaires et ainsi de suite. Mais ce serait bien si vous pouviez commenter les utilisations de la réflexion aussi.

646voto

jalf Points 142628

La réflexion en C++ pose plusieurs problèmes.

  • C'est beaucoup de travail à ajouter, et le comité C++ est assez conservateur, et ne consacre pas de temps à de nouvelles fonctionnalités radicales à moins d'être sûr que cela sera rentable. (Une suggestion d'ajouter un système de modules similaire aux assemblages .NET a été faite, et bien que je pense qu'il y ait un consensus général sur le fait que ce serait bien de l'avoir, ce n'est pas leur première priorité pour le moment, et a été repoussé jusqu'à bien après C++0x). La motivation de cette fonctionnalité est de se débarrasser de la fonction #include mais il permettrait également d'obtenir au moins quelques métadonnées).

  • On ne paie pas pour ce que l'on ne paie pas pas. C'est l'une des principales philosophie de conception qui sous-tend le C++. Pourquoi mon code devrait-il transporter des métadonnées si je n'en ai jamais besoin ? De plus, l'ajout de métadonnées peut empêcher le compilateur d'optimiser d'optimiser. Pourquoi devrais-je payer ce dans mon code si je n'ai jamais besoin de ces ces métadonnées ?

  • Ce qui nous amène à un autre point important : Le C++ rend très peu de garanties sur le code compilé. Le compilateur est autorisé à faire des tout ce qu'il veut, tant que la la fonctionnalité résultante est celle attendue. Par exemple, vos ne sont pas obligées de faire être présent . Le compilateur peut les optimiser, mettre en ligne tout ce qu'ils font. tout ce qu'ils font, et il souvent, c'est ce qu'il fait, parce que même un code de modèle simple a tendance à à créer un grand nombre d'instanciations instanciations de modèles. La bibliothèque standard bibliothèque répond à l'appel sur cet agressif agressive. Les foncteurs sont seulement performants que si la surcharge de l'instanciation et la déstructuration des peuvent être optimisés. operator[] sur un vecteur n'est comparable qu'à une en termes de performances parce que l'opérateur entier peut être inline et donc entièrement supprimé du code compilé. C# et Java offrent de nombreuses garanties quant à la sortie de sortie du compilateur. Si je définis une classe en C#, alors cette classe volonté exister dans l'assemblage résultant. Même si je ne l'utilise jamais. Même si tous les appels à ses fonctions membres pourraient être intégrés. La classe doit être là, pour que la réflexion puisse la la trouver. Ce problème est en partie résolu par le fait que C# compilant en bytecode, ce qui signifie que le compilateur JIT peut supprimer les définitions de classes et les fonctions inline s'il le souhaite, même si l'élément compilateur C# initial ne le peut pas. En C++, vous n'avez qu'un seul compilateur, et il doit produire un code efficace. Si vous d'inspecter les métadonnées d'un exécutable d'un exécutable C++, vous vous attendriez à voir toutes les classes qu'il définit, ce qui ce qui signifie que le compilateur devrait préserver toutes les classes définies, même si elles ne sont pas nécessaires.

  • Et puis il y a les modèles. Les modèles en C++ n'ont rien à voir avec les génériques dans d'autres langages. Chaque instanciation d'un modèle crée un nuevo type. std::vector<int> est une classe complètement distincte de celle de std::vector<float> . Cela se traduit par beaucoup de types différents dans un programme. Que doit voir notre réflexion devrait-elle voir ? Les modèle std::vector ? Mais comment ? comment le pourrait-il, puisqu'il s'agit d'un construction du code source, qui n'a pas de signification au moment de l'exécution ? Il faudrait voir les classes séparées std::vector<int> et std::vector<float> . Et std::vector<int>::iterator et std::vector<float>::iterator , même pour const_iterator et ainsi de suite. et ainsi de suite. une fois que l'on entre dans le modèle métaprogrammation, on finit rapidement par à instancier des centaines de modèles, qui sont tous soulignés et supprimés par le compilateur. par le compilateur. Ils n'ont aucune signification, sauf en tant que partie d'un métaprogramme de compilation. Toutes ces ces centaines de classes doivent-elles être visibles à la réflexion ? Il le faut, car sinon notre réflexion serait inutile, si elle ne garantit même pas que les classes que j'ai définies seront effectivement être présent . Un autre problème est que la classe modèle n'existe pas tant qu'elle n'est pas instanciée. Imaginez un programme qui utilise std::vector<int> . Notre système de réflexion devrait-il être capable de voir std::vector<int>::iterator ? D'une part, on peut s'attendre à ce que ce soit le cas. Il s'agit d'une classe importante, définie en termes de std::vector<int> qui fait existent dans les métadonnées. D'autre part, si le programme n'est jamais réellement utilise ce modèle de classe d'itérateur, son type n'aura jamais été instancié, et le compilateur n'aura donc pas généré la classe en premier lieu. Et il est trop tard pour la créer à l'exécution, puisque cela nécessite l'accès au code source.

  • Enfin, la réflexion n'est pas tout à fait aussi vitale en C++ qu'en C#. La raison raison est encore une fois que les modèles la métaprogrammation par gabarits. Elle ne peut pas résoudre tout, mais dans de nombreux cas où vous auriez autrement recours à la métaprogrammation par il est possible d'écrire un métaprogramme qui fait la même chose la même chose à la compilation. boost::type_traits est un simple simple. Vous voulez connaître le type T ? Vérifier son type_traits . En C#, il faudrait chercher son type à l'aide de la réflexion. type en utilisant la réflexion. La réflexion serait toujours utile pour certaines choses (la principale utilisation que je vois, que la métaprogrammation ne peut pas facilement remplacer, est pour les sérialisation autogénérée), mais elle des coûts significatifs pour le C++, et ce n'est tout simplement pas nécessaire aussi souvent que dans d'autres langages.

Edita: En réponse aux commentaires :

cdleary : Oui, les symboles de débogage font quelque chose de similaire, en ce sens qu'ils stockent des métadonnées sur les types utilisés dans l'exécutable. Mais ils souffrent également des problèmes que j'ai décrits. Si vous avez déjà essayé de déboguer une version de l'exécutable, vous comprendrez ce que je veux dire. Il y a de grandes lacunes logiques là où vous avez créé une classe dans le code source, qui a été supprimée dans le code final. Si vous deviez utiliser la réflexion pour quelque chose d'utile, vous auriez besoin qu'elle soit plus fiable et cohérente. Dans l'état actuel des choses, les types disparaissent presque à chaque compilation. Vous changez un tout petit détail, et le compilateur décide de changer les types qui sont soulignés et ceux qui ne le sont pas, en réponse. Comment en tirer quelque chose d'utile, alors que vous n'avez même pas la garantie que les types les plus pertinents seront représentés dans vos métadonnées ? Le type que vous recherchiez était peut-être présent dans la dernière version, mais maintenant il a disparu. Et demain, quelqu'un vérifiera une petite modification innocente à une petite fonction innocente, qui rendra le type juste assez grand pour qu'il ne soit pas complètement inline, et il sera donc de retour. C'est toujours utile pour les symboles de débogage, mais pas beaucoup plus que cela. Je détesterais essayer de générer du code de sérialisation pour une classe dans ces conditions.

Evan Teran : Bien sûr, ces questions pourrait être résolue. Mais cela nous ramène à mon premier point. Cela demanderait beaucoup de travail, et le comité C++ a beaucoup de choses qu'il juge plus importantes. Le bénéfice d'une réflexion limitée (et elle serait limitée) en C++ est-il vraiment assez important pour justifier que l'on s'y concentre au détriment d'autres fonctionnalités ? Y a-t-il vraiment un avantage énorme à ajouter des fonctionnalités au langage de base qui peuvent déjà (en grande partie) être réalisées par des bibliothèques et des préprocesseurs comme ceux de QT ? Peut-être, mais le besoin est beaucoup moins urgent que si ces bibliothèques n'existaient pas. Pour ce qui est de vos suggestions spécifiques, je pense que les interdire pour les templates les rendrait complètement inutiles. Vous ne pourriez pas utiliser la réflexion dans la bibliothèque standard, par exemple. Quel type de réflexion ne vous permettrait pas de voir un std::vector ? Les modèles sont une énorme partie de C++. Une fonction qui ne fonctionne pas avec les modèles est fondamentalement inutile.

Mais vous avez raison, une certaine forme de réflexion pourrait être mise en œuvre. Mais ce serait un changement majeur dans le langage. Dans l'état actuel des choses, les types sont exclusivement une construction à la compilation. Ils existent pour le bénéfice du compilateur, et rien d'autre. Une fois que le code a été compilé, il y a a pas de cours. En s'étirant un peu, on pourrait dire que les fonctions existent toujours, mais en réalité, tout ce qu'il y a, c'est un tas d'instructions de saut en assembleur, et un grand nombre de push/pop de la pile. Il n'y a pas grand-chose sur quoi s'appuyer pour ajouter de telles métadonnées.

Mais comme je l'ai dit, il est proposé de modifier le modèle de compilation, en ajoutant des modules autonomes, en stockant des métadonnées pour les types sélectionnés, en permettant à d'autres modules de les référencer sans avoir à s'embarrasser de #include s. C'est un bon début et, pour être honnête, je suis surpris que le comité de normalisation n'ait pas rejeté la proposition parce qu'elle représentait un changement trop important. Alors peut-être dans 5-10 ans ? :)

2 votes

La plupart de ces problèmes ne doivent-ils pas déjà être résolus par des symboles de débogage ? Ce n'est pas que ce serait performant (à cause de l'inlining et de l'optimisation que vous avez mentionnés), mais vous pourriez autoriser l'utilisation des symboles de débogage. possibilité de réflexion en faisant ce que font les symboles de débogage.

1 votes

Réponse fantastique, même si je pense que tous les problèmes techniques pourraient être résolus. Vous pourriez en faire un mot-clé qui active la réflexion sur une classe. Vous pourriez obtenir des erreurs de compilation si la réflexion est utilisée sur une classe qui n'a pas de mot-clé. Vous pourriez également interdire la combinaison avec des modèles.

0 votes

Enfin, il pourrait y avoir une règle selon laquelle les classes qui permettent la réflexion ne doivent pas être optimisées au point d'être réduites à néant. Il s'agirait donc d'une dépense supplémentaire, si vous l'avez demandé.

43voto

Mehrdad Afshari Points 204872

La réflexion exige que certaines métadonnées sur les types soient stockées quelque part et puissent être interrogées. Étant donné que le C++ se compile en code machine natif et subit d'importantes modifications dues à l'optimisation, la vue de haut niveau de l'application est pratiquement perdue dans le processus de compilation, et il ne sera donc pas possible de l'interroger au moment de l'exécution. Java et .NET utilisent une représentation de très haut niveau dans le code binaire pour les machines virtuelles, ce qui rend ce niveau de réflexion possible. Dans certaines implémentations du C++, cependant, il existe quelque chose appelé Run Time Type Information (RTTI) qui peut être considéré comme une version simplifiée de la réflexion.

16 votes

RTTI fait partie de la norme C++.

1 votes

Mais toutes les implémentations du C++ ne sont pas standardisées. J'ai vu des implémentations qui ne supportaient pas RTTI.

4 votes

La plupart des implémentations qui prennent en charge la RTTI permettent également de la désactiver via les options du compilateur.

23voto

Mordachai Points 3234

Toutes les langues ne devraient pas essayer d'intégrer toutes les caractéristiques de toutes les autres langues.

Le C++ est essentiellement un macro-assembleur très, très sophistiqué. Ce n'est PAS (au sens traditionnel du terme) un langage de haut niveau comme C#, Java, Objective-C, Smalltalk, etc.

Il est bon d'avoir différents outils pour différents travaux. Si nous n'avons que des marteaux, tout ressemblera à des clous, etc. Avoir des langages script est utile pour certaines tâches, et des langages OO réfléchis (Java, Obj-C, C#) sont utiles pour une autre catégorie de tâches, et des langages super efficaces proches de la machine sont utiles pour une autre catégorie de tâches (C++, C, Assembler).

Le C++ fait un travail remarquable en étendant la technologie Assembler à des niveaux incroyables de gestion de la complexité et d'abstractions pour rendre la programmation de tâches plus vastes et plus complexes beaucoup plus possible pour les êtres humains. Mais ce n'est pas nécessairement un langage qui convient le mieux à ceux qui abordent leur problème d'un point de vue strictement de haut niveau (Lisp, Smalltalk, Java, C#). Si vous avez besoin d'un langage doté de ces caractéristiques pour mettre en œuvre au mieux une solution à vos problèmes, remerciez ceux qui ont créé de tels langages pour que nous puissions tous les utiliser !

Mais le C++ est destiné à ceux qui, pour quelque raison que ce soit, ont besoin d'une forte corrélation entre leur code et le fonctionnement de la machine sous-jacente. Qu'il s'agisse d'efficacité, de programmation de pilotes de périphériques, d'interaction avec les services de bas niveau du système d'exploitation ou autre, le C++ est mieux adapté à ces tâches.

C#, Java, Objective-C nécessitent tous un système d'exécution beaucoup plus vaste et plus riche pour soutenir leur exécution. Ce système d'exécution doit être livré au système en question - préinstallé pour soutenir le fonctionnement de votre logiciel. Et cette couche doit être maintenue pour différents systèmes cibles, personnalisée par QUELQUE AUTRE LANGUE pour qu'elle fonctionne sur cette plateforme. Et cette couche intermédiaire - cette couche adaptative entre le système d'exploitation hôte et votre code - le temps d'exécution, est presque toujours écrite dans un langage comme le C ou le C++ où l'efficacité est au premier plan, où la compréhension prévisible de l'interaction exacte entre le logiciel et le matériel peut être bien comprise, et manipulée pour un gain maximal.

J'aime Smalltalk, Objective-C, et avoir un système d'exécution riche avec la réflexion, les méta-données, le ramassage des ordures, etc. Il est possible d'écrire des codes étonnants pour tirer parti de ces facilités ! Mais il s'agit simplement d'une couche supérieure sur la pile, une couche qui doit reposer sur des couches inférieures, qui elles-mêmes doivent en fin de compte reposer sur le système d'exploitation et le matériel. Et nous aurons toujours besoin d'un langage qui soit le mieux adapté à la construction de cette couche : C++/C/Assembleur.

Addendum : C++11/14 continue d'étendre la capacité du C++ à prendre en charge des abstractions et des systèmes de plus haut niveau. Le threading, la synchronisation, des modèles de mémoire précis, des définitions de machines abstraites plus précises permettent aux développeurs C++ de réaliser de nombreuses abstractions de haut niveau que certains de ces langages de haut niveau seulement avaient l'exclusivité, tout en continuant à fournir des performances proches du métal et une excellente prévisibilité (c'est-à-dire des sous-systèmes d'exécution minimaux). Peut-être que les fonctions de réflexion seront activées de manière sélective dans une future révision du C++, pour ceux qui le souhaitent - ou peut-être qu'une bibliothèque fournira de tels services d'exécution (il y en a peut-être déjà une, ou les prémices d'une dans boost ?)

0 votes

Votre remarque sur le fait que le runtime d'un langage doit être compilé dans un autre langage n'est pas vraie dans le cas de l'Objective-C, puisque son runtime est écrit en C (dont l'Objective-C est un surensemble).

0 votes

Il s'agit d'une distinction sans différence. Quelle différence cela fait-il lorsque, en fin de compte, le sous-système d'exécution utilisé par Objective-C n'est pas écrit en Objective-C, mais en C ?

0 votes

Objectif-C est C, et c'est bien là l'essentiel. Tout programme C est valide en Objective-C, de sorte que son système d'exécution est en fait compilable dans son propre langage.

12voto

Michael Kohne Points 8233

Si vous voulez vraiment comprendre les décisions de conception qui entourent le C++, trouvez un exemplaire de l'ouvrage intitulé Le manuel de référence C++ annoté par Ellis et Stroustrup. Il n'est PAS à jour avec la dernière norme, mais il passe en revue la norme originale et explique comment les choses fonctionnent et, souvent, comment elles sont devenues ainsi.

8 votes

Aussi Conception et évolution du C++ par Stroustrup

7voto

Klaim Points 24511

La réflexion peut être et a déjà été mise en œuvre en C++.

Ce n'est pas une fonctionnalité native de C++ car elle a un coût important (mémoire et vitesse) qui ne devrait pas être fixé par défaut par le langage - le langage est orienté "performance maximale par défaut".

Comme vous ne devriez pas payer pour ce dont vous n'avez pas besoin, et comme vous le dites vous-même, la réflexion est plus nécessaire dans les éditeurs que dans d'autres applications, elle devrait être implémentée uniquement là où vous en avez besoin, et ne pas être "imposée" à tout le code (vous n'avez pas besoin de réflexion sur toutes les données avec lesquelles vous travaillerez dans un éditeur ou une autre application similaire).

3 votes

Et vous n'expédiez pas de symboles parce que cela permettrait à vos clients/concurrents de consulter votre code... ce qui est souvent considéré comme une mauvaise chose.

0 votes

Vous avez raison, je n'avais même pas pensé au problème de l'exposition du code :)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X