Commentaires
- cela a probablement été fait de la même manière quen C brut, voir Lécriture de code générique lorsque votre cible est un compilateur C
- @Telastyn, IIRC, les modèles étaient une nouveauté de CFront 3 (vers 1990).
- @Telastyn: vers lan 2000, la plupart des compilateurs C ++ existants ne fournissaient pas très bien les modèles (même sils prétendaient les fournir, la plupart dentre eux étaient trop bogués pour une utilisation en production). La prise en charge des modèles était principalement destinée à la prise en charge des conteneurs génériques, mais loin des exigences pour prendre en charge quelque chose comme Alexandrescu ' s " Conception C ++ moderne " exemples.
- Etes-vous sûr que la génération de code à la compilation a été utilisée avant que la puissance des modèles ne soit réalisée? Jétais jeune à lépoque, mais jai limpression que dans lancien temps, seuls les types les plus simples de génération de code de compilation étaient utilisés. Si vous vouliez vraiment écrire un programme pour écrire votre programme, vous avez écrit un programme / script dont la sortie était du code source C, plutôt que de le faire avec un langage modèle.
- @Joshua – Les questions sont fermées en double si les réponses existantes à une autre question portent sur les principales questions qui se cachent derrière la question actuelle. " Les correspondances " exactes ne sont pas requises; le but est de faire correspondre rapidement le PO avec les réponses à leur question. En dautres termes, oui, cest ' un doublon. Cela dit, cette question sent " OMG! Comment le Monde existait-il avant …?! " qui nest ' quune question terriblement constructive. Doc Brown a souligné dans un commentaire antérieur (et maintenant supprimé) comment ce phénomène peut affecter les commentaires.
Réponse
Outre le pointeur void *
qui est couvert dans la réponse de Robert , une technique comme celle-ci a été utilisée (Clause de non-responsabilité: Mémoire de 20 ans):
#define WANTIMP #define TYPE int #include "collection.h" #undef TYPE #define TYPE string #include "collection.h" #undef TYPE int main() { Collection_int lstInt; Collection_string lstString; }
Où jai oublié la magie exacte du préprocesseur à lintérieur de collection.h
, mais cétait quelque chose comme ceci:
class Collection_ ## TYPE { public: Collection_ ## TYPE () {} void Add(TYPE value); private: TYPE *list; size_t n; size_t a; } #ifdef WANTIMP void Collection_ ## TYPE ::Add(TYPE value) #endif
Commentaires
- +1 – Il y a un livre de Bjarne Stroustrup où il raconte lhistoire du C ++ (trouvé il y a des années à la bibliothèque universitaire), et cette technique a été explicitement décrite comme une motivation pour le développement de modèles.
- Cette technique a-t-elle un nom? Peut-être " X-Macros " ?
Réponse
Le la manière traditionnelle dimplémenter des génériques sans avoir de génériques (la raison pour laquelle les modèles ont été créés) est dutiliser un pointeur void.
typedef struct Item{ void* data; } Item; typedef struct Node{ Item Item; struct Node* next; struct Node* previous; } Node;
Dans cet exemple de code, un arbre binaire ou une liste à double lien peut être représentée. Comme item
encapsule un pointeur void, nimporte quel type de données peut y être stocké. Bien sûr, vous devrez connaître le type de données lors de lexécution pour pouvoir le renvoyer vers un objet utilisable.
Commentaires
- Cela fonctionne pour le stockage générique, mais quen est-il des fonctions génériques? Les macros de @gnat ' peuvent gérer cela, mais cette horrible petite classe peut ' t. ' que cela a également conduit à un cauchemar de problèmes dalias stricts lors du traitement des données?
- @MadScienceDreams Pourquoi ' t vous appliquez ceci aux adresses de fonction?
- @IdeaHat: Oui, ça le ferait. Mais tout cela vient dune époque où laccent était beaucoup moins mis sur le fait que les outils sauvent le programmeur de ses propres erreurs. En dautres termes, il fallait être prudent car la langue vous donnait beaucoup plus de corde pour se tirer une balle dans le pied.
- Vous pouviez connaître le type de données à lautre extrémité du pointeur en stockant quelque part .. peut-être dans une table? Peut-être une … vtable? Cest le genre de choses que le compilateur résume pour nous. En dautres termes, avant que les compilateurs ne gèrent les modèles et le polymorphisme, nous devions créer les nôtres.
- @IdeaHat: Pour les fonctions génériques, regardez qsort dans la bibliothèque C. Il peut tout trier car il prend un pointeur de fonction pour la fonction de comparaison et transmet une paire de void * ' s.
Réponse
Comme dautres réponses lont souligné, vous pouvez utiliser void*
pour les structures de données génériques.Pour dautres types de polymorphisme paramétrique, des macros de préprocesseur étaient utilisées si quelque chose était répété beaucoup (comme des dizaines de fois). Pour être honnête, cependant, la plupart du temps pour les répétitions modérées, les gens ont simplement copié et collé, puis changé les types, car il y a beaucoup de pièges avec les macros qui les rendent problématiques.
Nous avons vraiment besoin dun nom pour linverse du paradoxe blub , où les gens ont du mal à imaginer la programmation dans un langage moins expressif, car cela revient souvent sur ce site. Si vous navez jamais utilisé un langage avec des moyens expressifs dimplémenter le polymorphisme paramétrique, vous ne savez pas vraiment ce qui vous manque. Vous acceptez simplement le copier-coller comme quelque peu ennuyeux, mais nécessaire.
Il y a des inefficacités dans vos langues de choix actuelles dont vous nêtes même pas encore conscient. Dans vingt ans, les gens se demanderont comment vous les avez éliminés. La réponse courte est que vous ne l’avez pas fait, car vous ne saviez pas que vous le pouviez.
Commentaires
-
We really need a name for the opposite of the blub paradox, where people have a hard time imagining programming in a less expressive language, because this comes up a lot on this site.
Cela me semble être exactement le paradoxe blub (" Les langues moins puissantes que Blub sont évidemment moins puissantes, car elles ' re il manque une fonctionnalité dont il ' lhabitude. ") Il ' est lopposé si la personne connaissait les limites de sa langue ' et pouvait imaginer des fonctionnalités qui les résolvent. - En face nest pas ' t tout à fait le mot que je cherchais, mais ' est le plus proche que je puisse trouver. Le paradoxe blub suppose la capacité de reconnaître un langage moins puissant, mais ne ' t commenter la difficulté de comprendre la programmation en une. Un peu contre-intuitif, la capacité de programmer dans un langage plus puissant ne confère ' une capacité proportionnée à programmer dans un langage moins puissant.
- Je soupçonne " converse " est le mot que vous ' recherchez: " Une situation, un objet ou une déclaration qui est linverse dune autre ou qui lui correspond mais avec certains termes transposés "
Réponse
Je me souviens quand gcc a été livré avec genclass
– un programme qui prenait en entrée un ensemble de types de paramètres ( par exemple clé et valeur pour une carte) et un fichier de syntaxe spécial qui décrit un type paramétré (par exemple, une carte ou un vecteur) et génère une implémentation C ++ valide avec les types de paramètres remplis.
Donc, si vous nécessaire Map<int, string>
et Map<string, string>
(ce nétait pas la syntaxe réelle, sachez que) vous deviez exécutez ce programme deux fois pour générer quelque chose comme map_string_string.h et map_int_string.h, puis utilisez-les dans votre code.
Voir la page de manuel pour genclass
et la documentation de GNU C ++ Library 2.0 pour plus de détails.
Réponse
[Au PO: Je « nessaye pas de vous harceler personnellement, mais de sensibiliser votre et les autres » à réfléchir à la logique de la question ( s) demandé sur SE et ailleurs. Merci de ne pas prendre cela personnellement!]
Le titre de la question est bon, mais vous limitez sévèrement la portée de vos réponses en incluant « … les situations où elles avaient besoin de la génération de code de compilation. « Beaucoup de bonnes réponses à la question de savoir comment faire la génération de code à la compilation en C ++ sans modèles existent sur cette page, mais pour répondre à la question que vous avez posée à lorigine:
Que faisaient les gens avant les modèles en C ++?
La réponse est, bien sûr, quils (nous) ne les avons pas utilisés. Oui, je suis ironique, mais les détails de la question dans le corps semblent (peut-être exagérément) supposer que tout le monde aime les modèles et quaucun codage naurait jamais pu être fait sans eux.
À titre dexemple, jai réalisé de nombreux projets de codage dans différents langages sans avoir besoin de génération de code à la compilation, et je pense que dautres lont également fait. Bien sûr, le problème résolu par les modèles était une démangeaison suffisamment grande pour que quelquun lait réellement rayé, mais le scénario posé par cette question était, en grande partie, inexistant.
Considérons une question similaire dans les voitures:
Comment les conducteurs sont-ils passés dune vitesse à une autre, en utilisant une méthode automatisée qui a changé les vitesses pour vous, avant que la transmission automatique ne soit inventée?
La question est, bien sûr, idiote. Demander comment une personne a fait X avant que X ne soit inventé nest pas vraiment une question valable. La réponse est généralement, « nous ne lavons pas fait et ne lavons pas manqué parce que nous ne savions pas quil existerait un jour ».Oui, il est facile de voir les avantages après coup, mais supposer que tout le monde se tenait là, donnant des coups de pied, attendant une transmission automatique ou des modèles C ++, ce nest vraiment pas vrai.
A la question « comment les conducteurs ont-ils changé de vitesse avant linvention de la transmission automatique? », On peut raisonnablement répondre, « manuellement », et cest le type de réponses que vous obtenez ici. Cest peut-être même le type de question que vous vouliez poser.
Mais ce nest pas celle que vous avez posée.
Donc:
Q: Comment les gens utilisaient-ils les modèles avant que les modèles ne soient inventés?
R: Nous ne lavons pas fait.
Q: Comment les gens utilisaient-ils les modèles avant que les modèles ne soient inventés, alors que ils avaient besoin de utiliser des modèles ?
R: Nous navons pas besoin de les utiliser. Pourquoi supposer que nous lavons fait? (Pourquoi supposer que nous le faisons?)
Q: Quels sont les moyens alternatifs pour obtenir les résultats fournis par les modèles?
R: De nombreuses bonnes réponses existent ci-dessus.
Sil vous plaît, pensez aux erreurs logiques dans vos messages avant de publier.
[Merci! Sil vous plaît, pas de mal voulu ici.]
Commentaires
- Je pense que vous ' est indûment pédant. Le PO na pas formulé sa question aussi bien quil aurait pu. Cependant, je pense que ' est assez clair quil voulait poser quelque chose comme " comment les gens créaient-ils des fonctions génériques avant modèles ". Je pense que dans ce cas, laction appropriée aurait été de modifier sa réponse ou de laisser un commentaire plutôt que de donner une conférence plutôt condescendante.
Réponse
Les macros horribles ont raison, de http://www.artima.com/intv/modern2.html :
Bjarne Stroustrup: Oui. Quand vous dites «modèle de type T», cest vraiment le vieux calcul mathématique, «pour tout T.» Cest la façon dont il est considéré. Mon tout premier article sur « C with Classes » (qui a évolué vers C ++) de 1981 mentionnait des types paramétrés. Là, jai bien compris le problème, mais jai trouvé la solution totalement erronée. Jai expliqué comment vous pouvez paramétrer des types avec des macros, et un garçon qui était un mauvais code.
Vous pouvez voir comment une ancienne version de macro dun modèle a été utilisée ici : http://www.xvt.com/sites/default/files/docs/Pwr++_Reference/rw/docs/html/toolsref/rwgvector.html
Réponse
Comme la déjà dit Robert Harvey, un pointeur vide est le type de données générique.
Un exemple de la bibliothèque C standard, comment trier un tableau de double avec un tri générique:
double *array = ...; int size = ...; qsort (array, size, sizeof (double), compare_doubles);
Où compare_double
est défini comme:
int compare_doubles (const void *a, const void *b) { const double *da = (const double *) a; const double *db = (const double *) b; return (*da > *db) - (*da < *db); }
La signature de qsort
est définie dans stdlib.h:
void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *) );
Notez quil ny a pas de type vérification au moment de la compilation, pas même au moment de lexécution. Si vous triez une liste de chaînes avec le comparateur ci-dessus qui attend des doubles, il essaiera avec plaisir dinterpréter la représentation binaire dune chaîne comme un double et de trier en conséquence.
Réponse
Une façon de faire est comme ceci:
https://github.com/rgeminas/gp–/blob/master/src/scope/darray.h
#define DARRAY_DEFINE(name, type) DARRAY_TYPEDECL(name, type) DARRAY_IMPL(name, type) // This is one single long line #define DARRAY_TYPEDECL(name, type) \ typedef struct darray_##name \ { \ type* base; \ size_t allocated_mem; \ size_t length; \ } darray_##name; // This is also a single line #define DARRAY_IMPL(name, type) \ static darray_##name* darray_init_##name() \ { \ darray_##name* arr = (darray_##name*) malloc(sizeof(darray_##name)); \ arr->base = (type*) malloc(sizeof(type)); \ arr->length = 0; \ arr->allocated_mem = 1; \ return arr; \ }
La macro DARRAY_TYPEDECL crée effectivement une définition de structure (sur une seule ligne), en remplaçant name
avec le nom que vous passez et en stockant un tableau des type
que vous passez (le name
est là pour que vous puissiez le concaténer au nom de la structure de base et ont toujours un identifiant valide – darray_int * nest pas un nom valide pour une structure), tandis que la macro DARRAY_IMPL définit les fonctions qui opèrent sur cette structure (dans ce cas, elles « ont été marquées comme statiques juste pour que lon nappeler la définition quune seule fois et ne pas tout séparer).
Ceci serait utilisé comme:
#include "darray.h" // No types have been defined yet DARRAY_DEFINE(int_ptr, int*) // by this point, the type has been declared and its functions defined darray_int_ptr* darray = darray_int_ptr_init();
Réponse
Je pense que les modèles sont souvent utilisés pour réutiliser des types de conteneurs qui ont un beaucoup de valeurs algorithmiques telles que les tableaux dynamiques (vecteurs), les cartes, les arbres, etc. tri, etc.
Sans modèles, nécessairement, ceux-ci contiennent des implémentations sont écrites de manière générique et reçoivent juste assez dinformations sur le type requis pour leur domaine. Par exemple, avec un vecteur, ils ont juste besoin que les données soient blt « capables et ils ont besoin de connaître la taille de chaque élément.
Disons que vous avez une classe de conteneur appelée Vector qui fait cela. Il faut du vide *. Lutilisation simpliste de ceci serait que le code de la couche application fasse beaucoup de cast. Donc, sils gèrent des objets Cat, ils doivent lancer Cat * to void *, et revenir partout. Le fait de jeter du code dapplication avec des casts pose des problèmes évidents.
Les modèles résolvent ce problème.
Une autre façon de le résoudre est de créer un type de conteneur personnalisé pour le type que vous stockez dans le conteneur. Donc, si vous avez une classe Cat, vous créez une classe CatList dérivée de Vector.Vous surchargez ensuite les quelques méthodes que vous utilisez, en introduisant des versions qui prennent des objets Cat au lieu de void *. Donc, vous surchargez la méthode Vector :: Add (void *) avec Cat :: Add (Cat *), qui passe simplement en interne le paramètre à Vector :: Add (). Ensuite, dans le code de votre application, vous appelez le version surchargée de Add lors du passage dun objet Cat et évite ainsi le casting. Pour être honnête, la méthode Add ne nécessiterait pas de conversion car un objet Cat * se convertit en void * sans conversion. Mais la méthode permettant de récupérer un élément, comme la surcharge dindex ou une méthode Get (), le ferait.
Lautre approche, le seul exemple dont je me souviens du début des années 90 avec un grand framework dapplication, consiste à utiliser un utilitaire personnalisé qui crée ces types sur des classes. Je pense que MFC la fait. Ils avaient différentes classes pour les conteneurs comme CStringArray, CRectArray, CDoulbeArray, CIntArray, etc. Plutôt que de conserver du code en double, ils ont fait un type de méta-programmation similaire aux macros, en utilisant un outil externe qui générerait les classes. Ils ont rendu loutil disponible avec Visual C ++ au cas où quelquun Je voulais lutiliser – je ne lai jamais fait. Peut-être que jaurais dû. Mais à lépoque, les experts vantaient « Sane sous-ensemble de C ++ » et « Vous navez » pas besoin de modèles «