it-swarm.com.de

Was haben die Leute vor Vorlagen in C ++ gemacht?

Ich bin nicht neu in der Programmierung, aber ich bin eine, die vor ein paar Jahren begonnen hat, und ich liebe Vorlagen.

Aber wie haben die Menschen in früheren Zeiten mit Situationen umgegangen, in denen sie eine Codegenerierung zur Kompilierungszeit wie Vorlagen benötigten? Ich vermute schreckliche, schreckliche Makros (zumindest würde ich das so machen), aber wenn ich die obige Frage google, bekomme ich nur Seiten und Seiten mit Vorlagen-Tutorials.

Es gibt viele Argumente gegen die Verwendung von Vorlagen, und obwohl dies normalerweise auf die Lesbarkeit " YAGNI " hinausläuft und sich darüber beschwert, wie schlecht es implementiert ist, gibt es keine viel da draußen über Alternativen mit ähnlicher Kraft. Wenn ich eine Art Generika zur Kompilierungszeit ausführen muss und möchte meinen Code behalten TROCKEN , wie vermeidet/hat man die Verwendung von Vorlagen vermieden?

44
IdeaHat

Neben dem ungültigen * Zeiger in Roberts Antwort wurde eine Technik wie diese verwendet (Haftungsausschluss: 20 Jahre altes Gedächtnis):

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

Wo ich die genaue Präprozessor-Magie in der Sammlung vergessen habe.h, aber es war ungefähr so:

class Collection_ ## TYPE {
public:
 Collection() {}
 void Add(TYPE value);
private:
 TYPE *list;
 size_t n;
 size_t a;
}

#ifdef WANTIMP
void Collection_ ## TYPE ::Add(TYPE value)
#endif
52
Joshua

Die traditionelle 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 kann ein Binärbaum oder eine doppelt verknüpfte Liste 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.

46
Robert Harvey

Wie in anderen Antworten erwähnt, können Sie void* für generische Datenstrukturen. Für andere Arten von parametrischem Polymorphismus wurden Präprozessormakros verwendet, wenn etwas in einem Los 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 einen Namen für die Umkehrung des blub paradox , bei dem es den Leuten 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 genau, was Sie vermissen. 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: Du hast es nicht getan, weil du nicht wusstest, dass du es kannst.

18
Karl Bielefeldt

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

Wenn Sie also Map<int, string> Und Map<string, string> Benötigten (dies war jedoch nicht die eigentliche Syntax), mussten Sie dieses Programm zweimal ausführen, um so etwas wie map_string_string.h und map_int_string.h zu generieren und diese dann zu verwenden in Ihrem Code.

Hier ist die Manpage für genclass: http://manpages.ubuntu.com/manpages/dapper/man1/genclass.1.html

9
Rafał Dowgird

[Zum OP: Ich versuche nicht, Sie persönlich auszuwählen, sondern Sie und andere dafür zu sensibilisieren, über die Logik der Fragen nachzudenken, die in SE und anderswo gestellt werden. Bitte nimm das 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 muss". Auf dieser Seite finden Sie 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 Leute vor Vorlagen in C++ gemacht?

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 niemals eine 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 mithilfe einer automatisierten Methode, bei der die Gänge für Sie geschaltet wurden, 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 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, sich auf die Fersen traten, auf die automatische Übertragung oder auf C++ - Vorlagen warteten, ist wirklich nicht wahr.

Auf die Frage: Wie haben die Fahrer die Gänge geschaltet, bevor das Automatikgetriebe erfunden wurde? Man kann 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 der, den du gefragt hast.

Damit:

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

A: Das haben wir nicht.

F: Wie haben Benutzer Vorlagen verwendet, bevor Vorlagen erfunden wurden, als sie mussten Vorlagen verwenden?

A: Wir haben nicht müssen sie benutzen. Warum nehmen wir an, dass wir es getan haben? (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.

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

[Vielen Dank! Bitte, hier ist kein Schaden beabsichtigt.]

7
Joseph Cheek

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 erklärte, wie man Typen mit Makros parametrisieren kann, und Junge, das war mieser Code.

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

6
jas

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

Wo compare_double ist definiert 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 zur Kompilierungszeit keine Typprüfung stattfindet, auch nicht zur Laufzeit. Wenn Sie eine Liste von Zeichenfolgen mit dem obigen Komparator sortieren, der Doppelzeichen erwartet, wird gerne versucht, die binäre Darstellung einer Zeichenfolge als Doppelzeichen zu interpretieren und entsprechend zu sortieren.

5
Florian F

Eine Möglichkeit, dies zu tun, ist folgende:

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 DARRAY_TYPEDECL-Makro erstellt effektiv eine Strukturdefinition (in einer einzelnen Zeile), ersetzt name durch den Namen, den Sie übergeben, und speichert ein Array des type, das Sie übergeben (das name ist vorhanden, damit Sie es mit dem Namen der Basisstruktur verketten können und dennoch einen gültigen Bezeichner haben - 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 als statisch markiert, nur damit man die Definition nur einmal aufruft und nicht alles trennt).

Dies würde verwendet werden 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();
1
Renan Gemignani

Ich denke, Vorlagen werden häufig verwendet, um Containertypen wiederzuverwenden, die viele algorithmische Werte aufweisen, z. B. 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 Domäne erforderlichen Typ. Bei einem Vektor müssen beispielsweise nur die Daten verfügbar sein, und sie müssen die Größe jedes Elements kennen.

Angenommen, Sie haben eine Containerklasse namens Vector, die dies ausführt. 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 Verwerfen von Anwendungscode mit Abgüssen hat offensichtliche Probleme.

Vorlagen lösen dies.

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, erstellen Sie eine von Vector abgeleitete CatList-Klasse. 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 Methode Vector :: Add (void *) mit Cat :: Add (Cat *) überladen, die den Parameter intern einfach an Vector :: Add () übergibt. Dann würden Sie in Ihrem Anwendungscode die überladene Version von Add aufrufen, wenn Sie ein Cat-Objekt übergeben, und so das Casting vermeiden. 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 das getan. Sie hatten verschiedene Klassen für Container wie CStringArray, CRectArray, CDoulbeArray, CIntArray usw. Anstatt doppelten Code zu verwalten, führten sie eine Art Makroprogrammierung ähnlich wie Makros durch, wobei sie ein externes Tool verwendeten, das die Klassen generierte. Sie stellten das Tool mit Visual C++ zur Verfügung, falls jemand es verwenden wollte - ich habe es nie getan. Vielleicht hätte ich es tun sollen. Zu der Zeit warben die Experten jedoch für "Vernünftige Teilmenge von C++" und "Sie brauchen keine Vorlagen".

1
zumalifeguard