Was haben die Leute vor Vorlagen in C ++ gemacht? [duplizieren]

Diese Frage hat hier bereits Antworten :

Kommentare

  • Dies wurde wahrscheinlich genauso gemacht wie in einfachem C, siehe Das Schreiben von generischem Code, wenn Ihr Ziel ein C-Compiler ist
  • @ Telastyn, IIRC, Vorlagen waren eine Neuheit von CFront 3 (ungefähr 1990).
  • @Telastyn: Um das Jahr 2000 herum lieferten die meisten vorhandenen C ++ – Compiler keine sehr guten Vorlagen (selbst wenn sie vorgaben, diese bereitzustellen, waren die meisten von ihnen für die Verwendung in der Produktion zu fehlerhaft). Die Vorlagenunterstützung diente hauptsächlich der Unterstützung generischer Container, war jedoch weit entfernt von den Anforderungen zur Unterstützung von Alexandrescu ' s " Modernes C ++ – Design " Beispiele.
  • Sind Sie sicher, dass die Codegenerierung zur Kompilierungszeit verwendet wurde, bevor die Leistung von Vorlagen realisiert wurde? Ich war damals jung, aber ich habe den Eindruck, dass früher nur die einfachsten Arten der Codegenerierung zur Kompilierungszeit verwendet wurden. Wenn Sie wirklich ein Programm schreiben wollten, um Ihr Programm zu schreiben, haben Sie ein Programm / Skript geschrieben, dessen Ausgabe C-Quellcode war, anstatt dies mit der Vorlagensprache zu tun.
  • @Joshua – Fragen werden als Duplikat geschlossen, wenn Die vorhandenen Antworten auf eine andere Frage richten sich an die Hauptanfragen hinter der vorliegenden Frage. " Genaue " Übereinstimmungen sind nicht erforderlich. Es geht darum, das OP schnell mit Antworten auf ihre Frage abzugleichen. Mit anderen Worten, ja, es ist ' ein Duplikat. Das heißt, diese Frage riecht nach " OMG! Wie existierte die Welt vor … ?! " was ' keine schrecklich konstruktive Frage ist. Doc Brown hat in einem früheren (und jetzt gelöschten) Kommentar darauf hingewiesen, wie sich dieses Phänomen auf den Kommentar auswirken kann.

Antwort

Antwort

Die Die herkömmliche Methode zum Implementieren von Generika ohne Generika (der Grund, warum Vorlagen erstellt wurden) ist die Verwendung eines ungültigen Zeigers.

typedef struct Item{ void* data; } Item; typedef struct Node{ Item Item; struct Node* next; struct Node* previous; } Node; 

In diesem Beispielcode ein Binärbaum oder Eine doppelt verknüpfte Liste kann dargestellt werden. Da item einen ungültigen Zeiger kapselt, kann dort jeder Datentyp gespeichert werden. Natürlich müssten Sie den Datentyp zur Laufzeit kennen, damit Sie ihn wieder in ein verwendbares Objekt umwandeln können.

Kommentare

  • Das funktioniert für generischen Speicher, aber was ist mit generischen Funktionen? @gnat ' Makros können damit umgehen, aber diese schreckliche kleine Klasse kann ' t. ' führte dies nicht auch zu einem Albtraum strenger Aliasing-Probleme bei der Verarbeitung der Daten?
  • @MadScienceDreams Warum konnte nicht '
  • @IdeaHat: Ja, das würde es. All dies kommt jedoch aus einer Zeit, in der viel weniger Wert darauf gelegt wurde, dass die Tools den Programmierer vor seinen eigenen Fehlern bewahren. Mit anderen Worten, Sie mussten vorsichtig sein, da die Sprache Ihnen viel mehr Seil gab, um sich in den Fuß zu schießen.
  • Sie könnten den Datentyp am anderen Ende des Zeigers kennen, indem Sie ihn irgendwo speichern. Vielleicht in einem Tisch? Vielleicht eine … Tabelle? Dies ist das Zeug, das der Compiler für uns abstrahiert. Mit anderen Worten, bevor Compiler Vorlagen und Polymorphismus handhabten, mussten wir unsere eigenen erstellen.
  • @IdeaHat: Für generische Funktionen schauen Sie sich qsort in der C-Bibliothek an. Es kann alles sortieren, da es einen Funktionszeiger für die Vergleichsfunktion benötigt und ein Paar void * ' s.

übergibt Antwort

Wie in anderen Antworten erwähnt, können Sie void* für generische Datenstrukturen verwenden.Für andere Arten von parametrischem Polymorphismus wurden Präprozessormakros verwendet, wenn etwas oft wiederholt wurde (wie Dutzende Male). Um ehrlich zu sein, haben die Leute die meiste Zeit für mäßige Wiederholungen nur kopiert und eingefügt und dann die Typen geändert, weil es viele Fallstricke mit Makros gibt, die sie problematisch machen.

Wir brauchen wirklich eine Name für die Umkehrung des Blub-Paradoxons , bei dem es den Menschen schwer fällt, sich das Programmieren in einer weniger ausdrucksstarken Sprache vorzustellen, da dies auf dieser Site häufig vorkommt. Wenn Sie noch nie eine Sprache mit ausdrucksstarken Methoden zur Implementierung des parametrischen Polymorphismus verwendet haben, wissen Sie nicht wirklich, was Ihnen fehlt. Sie akzeptieren das Kopieren und Einfügen einfach als etwas nervig, aber notwendig.

Es gibt Ineffizienzen in Ihren aktuellen Sprachen Ihrer Wahl, die Sie noch nicht einmal kennen. In zwanzig Jahren werden sich die Leute fragen, wie Sie sie beseitigt haben. Die kurze Antwort lautet: Sie haben es nicht getan, weil Sie es nicht gewusst haben.

Kommentare

  • 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. Das scheint mir genau das Blub-Paradoxon zu sein (" Sprachen, die weniger mächtig sind als Blub, sind offensichtlich weniger mächtig, weil sie ' sind Fehlt eine Funktion, ist er ' gewohnt. ") ' wäre das Gegenteil Wenn die Person die Einschränkungen ihrer Sprache ' kennen und sich Funktionen vorstellen könnte, die sie lösen.
  • Gegenüber isn ' t Das Wort, für das ich mich entschieden habe, aber ' ist das nächste, das ich mir vorstellen kann. Das Blub-Paradoxon setzt die Fähigkeit voraus, eine weniger mächtige Sprache zu erkennen , kommentiert jedoch ' nicht die Schwierigkeit, Programmierung zu verstehen einer. Etwas kontraintuitiv verleiht die Fähigkeit, in einer leistungsfähigeren Sprache zu programmieren, ' keine angemessene Fähigkeit, in einer weniger leistungsfähigen Sprache zu programmieren.
  • Ich vermute " converse " ist das Wort, nach dem Sie ' suchen: " Eine Situation, ein Objekt oder eine Aussage, die das Gegenteil einer anderen ist oder dieser entspricht, jedoch mit bestimmten Begriffen transponiert ist "

Antwort

Ich erinnere mich, als gcc mit genclass ausgeliefert wurde – einem Programm, das eine Reihe von Parametertypen als Eingabe verwendete ( zB Schlüssel und Wert für eine Map) und eine spezielle Syntaxdatei, die einen parametrisierten Typ (z. B. eine Map oder einen Vektor) beschreibt und eine gültige C ++ – Implementierung mit den ausgefüllten Parametertypen generiert.

Wenn Sie also benötigt Map<int, string> und Map<string, string> (das war wohlgemerkt nicht die eigentliche Syntax) Führen Sie dieses Programm zweimal aus, um so etwas wie map_string_string.h und map_int_string.h zu generieren, und verwenden Sie diese dann in Ihrem Code.

iv id finden Sie in der Manpage = „ebaedd2ca2″>

und die Dokumentation aus der GNU C ++ Library 2.0 für weitere Details.

Antwort

[An das OP: Ich versuche nicht, Sie persönlich auszuwählen, sondern Ihr und andere Bewusstsein dafür zu schärfen, über die Logik der Frage nachzudenken ( s) auf SE und anderswo gefragt. Bitte nehmen Sie dies nicht persönlich!]

Der Titel der Frage ist gut, aber Sie schränken den Umfang Ihrer Antworten stark ein, indem Sie „… Situationen einschließen, in denen Code zur Kompilierungszeit generiert werden musste. „Auf dieser Seite gibt es viele gute Antworten auf die Frage, wie in C ++ Code zur Kompilierungszeit ohne Vorlagen generiert werden kann. Um jedoch die Frage zu beantworten, die Sie ursprünglich gestellt haben:

Was haben die Benutzer vor Vorlagen in C ++ getan?

Die Antwort ist natürlich, dass sie (wir) sie nicht benutzt haben. Ja, ich bin ein Augenzwinkern, aber die Details der Frage im Körper scheinen (vielleicht übertrieben) davon auszugehen, dass jeder Vorlagen liebt und dass ohne sie keine Codierung möglich gewesen wäre.

Als Beispiel habe ich viele, viele Codierungsprojekte in verschiedenen Sprachen abgeschlossen, ohne dass Code zur Kompilierungszeit generiert werden musste, und ich glaube, andere haben dies auch getan. Sicher, das durch Vorlagen gelöste Problem war ein Juckreiz, der groß genug war, dass jemand es tatsächlich zerkratzt hat, aber das von dieser Frage gestellte Szenario war größtenteils nicht vorhanden.

Betrachten Sie eine ähnliche Frage in Autos:

Wie haben die Fahrer vor der Erfindung des Automatikgetriebes mit einer automatisierten Methode, die für Sie den Gang geschaltet hat, von einem Gang in einen anderen geschaltet?

Die Frage ist natürlich albern. Die Frage, wie eine Person X gemacht hat, bevor X erfunden wurde, ist keine wirklich gültige Frage. Die Antwort lautet im Allgemeinen: „Wir haben es nicht getan und haben es nicht verpasst, weil wir nicht wussten, dass es jemals existieren würde.“Ja, es ist leicht, den Nutzen nachträglich zu erkennen, aber anzunehmen, dass alle herumstanden, auf die Fersen traten, auf die automatische Übertragung oder auf C ++ – Vorlagen warteten, ist wirklich nicht wahr.

Auf die Frage „Wie haben die Fahrer vor der Erfindung des Automatikgetriebes den Gang geschaltet?“ Kann man vernünftigerweise „manuell“ antworten, und das ist die Art von Antworten, die Sie hier erhalten. Es kann sogar die Art von Frage sein, die Sie stellen wollten.

Aber es war nicht die, die Sie gestellt haben.

Also:

F: Wie haben Benutzer Vorlagen verwendet, bevor Vorlagen erfunden wurden?

A: Wir haben es nicht getan.

F: Wie haben Benutzer Vorlagen verwendet, bevor Vorlagen erfunden wurden, wenn dies erforderlich war Verwenden Sie Vorlagen ?

A: Wir brauchten sie nicht . Warum nehmen wir an? (Warum nehmen wir an?)

F: Welche alternativen Möglichkeiten gibt es, um die Ergebnisse zu erzielen, die Vorlagen liefern?

A: Es gibt oben viele gute Antworten.

Denken Sie vor dem Posten über logische Irrtümer in Ihren Posts nach.

[Danke! Bitte, hier ist kein Schaden beabsichtigt.]

Kommentare

  • Ich denke, Sie ' ist übermäßig pedantisch. Das OP hat seine Frage nicht so gut formuliert, wie er es hätte tun können. Ich denke jedoch, dass ' ziemlich klar ist, dass er fragen wollte so etwas wie " wie haben die Leute vorher generische Funktionen erstellt? Vorlagen ". Ich denke, in diesem Fall wäre die geeignete Aktion gewesen, seine Antwort zu bearbeiten oder einen Kommentar zu hinterlassen, anstatt einen eher bevormundenden Vortrag zu halten.

Antwort

Schreckliche Makros sind richtig, von http://www.artima.com/intv/modern2.html :

Bjarne Stroustrup: Ja. Wenn Sie „Vorlagentyp T“ sagen, ist das wirklich die alte Mathematik, „für alle T.“ So wird es betrachtet. In meinem allerersten Artikel über „C mit Klassen“ (der sich zu C ++ entwickelte) aus dem Jahr 1981 wurden parametrisierte Typen erwähnt. Dort habe ich das Problem richtig verstanden, aber ich habe die Lösung völlig falsch verstanden. Ich habe erklärt, wie Sie Typen mit Makros parametrisieren können, und Junge, der mieser Code war.

Hier können Sie sehen, wie eine alte Makroversion einer Vorlage verwendet wurde : http://www.xvt.com/sites/default/files/docs/Pwr++_Reference/rw/docs/html/toolsref/rwgvector.html

Antwort

Wie Robert Harvey bereits sagte, ist ein ungültiger Zeiger der generische Datentyp.

Ein Beispiel aus der Standard-C-Bibliothek, wie ein Array von double mit einer generischen Sortierung sortiert wird:

double *array = ...; int size = ...; qsort (array, size, sizeof (double), compare_doubles); 

Wobei compare_double definiert ist als:

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); } 

Die Signatur von qsort ist in stdlib.h definiert:

void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *) ); 

Beachten Sie, dass es keinen Typ gibt Überprüfung zur Kompilierungszeit, nicht einmal zur Laufzeit. Wenn Sie eine Liste von Zeichenfolgen mit dem obigen Komparator sortieren, der Doppelte erwartet, wird gerne versucht, die binäre Darstellung einer Zeichenfolge als Doppel zu interpretieren und entsprechend zu sortieren.

Antwort

Eine Möglichkeit hierfür ist:

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; \ } 

Das Makro DARRAY_TYPEDECL erstellt effektiv eine Strukturdefinition (in einer einzelnen Zeile) und ersetzt name mit dem Namen, den Sie übergeben, und Speichern eines Arrays der type, die Sie übergeben (die name ist vorhanden, damit Sie sie verketten können auf den Namen der Basisstruktur und haben immer noch einen gültigen Bezeichner – darray_int * ist kein gültiger Name für eine Struktur), während das Makro DARRAY_IMPL die Funktionen definiert, die mit dieser Struktur arbeiten (in diesem Fall werden sie „statisch“ markiert, so wie man es tun würde Rufen Sie die Definition nur einmal auf und trennen Sie nicht alles.

Dies wird verwendet als:

#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(); 

Antwort

Ich denke, Vorlagen werden häufig verwendet, um Containertypen mit einem wiederzuverwenden Viele algorithmische Werte wie dynamische Arrays (Vektoren), Karten, Bäume usw., Sortierung usw.

Ohne Vorlagen enthalten diese Implementierungen notwendigerweise generisch geschrieben und erhalten gerade genug Informationen über den für ihre Domain erforderlichen Typ. Bei einem Vektor benötigen sie beispielsweise nur die Daten, um blt „fähig zu sein, und sie müssen die Größe jedes Elements kennen.

Nehmen wir an, Sie haben eine Containerklasse namens Vector, die dies tut. Es dauert nichtig *. Die vereinfachte Verwendung davon wäre, dass der Code der Anwendungsschicht viel Casting ausführt. Wenn sie also Cat-Objekte verwalten, müssen sie Cat * to void * und überall zurückwerfen. Das Verschmutzen von Anwendungscode mit Casts hat offensichtliche Probleme.

Vorlagen lösen dieses Problem.

Eine andere Möglichkeit, dies zu lösen, besteht darin, einen benutzerdefinierten Containertyp für den Typ zu erstellen, den Sie im Container speichern. Wenn Sie also eine Cat-Klasse haben, würden Sie eine von Vector abgeleitete CatList-Klasse erstellen.Anschließend überladen Sie die wenigen Methoden, die Sie verwenden, und führen Versionen ein, die Cat-Objekte anstelle von void * verwenden. Sie würden also die Vector :: Add (void *) -Methode mit Cat :: Add (Cat *) überladen, die den Parameter intern einfach an Vector :: Add () übergibt. Dann würden Sie in Ihrem Anwendungscode die aufrufen Überladene Version von Add beim Übergeben eines Cat-Objekts und somit Vermeiden des Castings. Um fair zu sein, würde die Add-Methode keine Umwandlung erfordern, da ein Cat * -Objekt ohne Umwandlung in void * konvertiert wird. Die Methode zum Abrufen eines Elements, z. B. die Indexüberladung oder eine Get () -Methode, würde dies jedoch tun.

Der andere Ansatz, an den ich mich aus den frühen 90er Jahren mit einem großen Anwendungsframework erinnere, ist die Verwendung eines benutzerdefinierten Dienstprogramms, das diese Typen über Klassen erstellt. Ich glaube, MFC hat dies getan. Sie hatten unterschiedliche Klassen für Container wie CStringArray, CRectArray, CDoulbeArray, CIntArray usw. Anstatt doppelten Code beizubehalten, führten sie eine Art Makroprogrammierung durch, die Makros ähnelte, und verwendeten ein externes Tool, das die Klassen generierte. Sie stellten das Tool mit Visual C ++ für alle Fälle zur Verfügung wollte es verwenden – ich habe es nie getan. Vielleicht hätte ich es tun sollen. Aber zu der Zeit warben die Experten für „Vernünftige Teilmenge von C ++“ und „Sie brauchen keine Vorlagen“

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.