it-swarm.com.de

Warum basiert die C++ - STL so stark auf Vorlagen? (und nicht auf * Schnittstellen *)

Ich meine, abgesehen von seinem verbindlichen Namen (der Standard Template Library) ...

C++ war ursprünglich beabsichtigt, OOP Konzepte in C zu präsentieren. Das heißt: Sie können anhand ihrer Klassen- und Klassenhierarchie feststellen, was eine bestimmte Entität tun kann und was nicht (unabhängig davon, wie sie funktioniert). Einige Kompositionen von Fähigkeiten sind auf diese Weise aufgrund der Problematik der Mehrfachvererbung und der Tatsache, dass C++ das Konzept der Schnittstellen auf etwas unbeholfene Weise (im Vergleich zu Java usw.) unterstützt, schwieriger zu beschreiben verbessert).

Und dann kamen Templates zusammen mit der STL ins Spiel. Die STL schien die klassischen OOP -Konzepte zu verwenden und sie unter Verwendung von Schablonen in den Abfluss zu spülen.

Es sollte unterschieden werden zwischen Fällen, in denen Vorlagen verwendet werden, um Typen zu generalisieren, bei denen die Typen Themenselves für den Betrieb der Vorlage nicht relevant sind (z. B. Container). Einen vector<int> zu haben macht durchaus Sinn.

In vielen anderen Fällen (Iteratoren und Algorithmen) sollen templatierte Typen jedoch einem "Konzept" folgen (Input-Iterator, Forward-Iterator usw.), in dem die tatsächlichen Details des Konzepts vollständig durch die Implementierung der Vorlage definiert werden Funktion/Klasse und nicht von der Klasse des Typs, der mit der Vorlage verwendet wird. Dies ist ein gewisser Anti-Gebrauch von OOP.

Zum Beispiel können Sie die Funktion feststellen:

void MyFunc(ForwardIterator<...> *I);

Update: Da es in der ursprünglichen Frage unklar war, kann ForwardIterator selbst als Vorlage verwendet werden, um einen beliebigen ForwardIterator-Typ zuzulassen. Das Gegenteil ist der Begriff ForwardIterator.

sie erwartet einen Forward-Iterator nur durch seine Definition, in der Sie entweder die Implementierung oder die Dokumentation nachschlagen müssen:

template <typename Type> void MyFunc(Type *I);

Zwei Behauptungen, die ich für die Verwendung von Vorlagen aussprechen kann: Kompilierter Code kann effizienter gestaltet werden, indem die Vorlage für jeden verwendeten Typ maßgeschneidert wird, anstatt vtables zu verwenden. Und die Tatsache, dass Vorlagen mit nativen Typen verwendet werden können.

Ich bin jedoch auf der Suche nach einem tieferen Grund, warum man klassisch OOP zugunsten der Vorlage für die STL aufgibt? (Vorausgesetzt, Sie lesen so weit: P)

205
OB OB

Die kurze Antwort lautet "weil C++ weitergezogen ist". Ja, in den späten 70ern wollte Stroustrup ein verbessertes C mit OOP -Funktionen erstellen, aber das ist lange her. Als die Sprache 1998 standardisiert wurde, war sie keine OOP Sprache mehr. Es war eine Multiparadigmasprache. Es hatte sicherlich eine gewisse Unterstützung für OOP Code, aber es hatte auch eine vollständig überlagerte Vorlagensprache, es ermöglichte Metaprogrammierung zur Kompilierungszeit und die Leute hatten generische Programmierung entdeckt. Plötzlich schien OOP einfach nicht mehr so ​​wichtig zu sein. Nicht, wenn wir einfacheren, präziseren und effizienteren Code schreiben können, indem wir Techniken verwenden, die über Vorlagen und generische Programmierung verfügbar sind.

OOP ist nicht der heilige Gral. Es ist eine nette Idee, und sie war in den 70er Jahren, als sie erfunden wurde, eine ziemliche Verbesserung gegenüber prozeduralen Sprachen. Aber es ist ehrlich gesagt nicht alles, worauf es ankommt. In vielen Fällen ist es umständlich und ausführlich und fördert nicht wirklich wiederverwendbaren Code oder Modularität.

Aus diesem Grund ist die C++ - Community heutzutage viel mehr an generischer Programmierung interessiert und warum alle endlich erkennen, dass funktionale Programmierung auch ziemlich clever ist. OOP allein ist einfach kein schöner Anblick.

Zeichnen Sie einen Abhängigkeitsgraphen einer hypothetischen "OOP-ified" -STL. Wie viele Klassen müssten über einander Bescheid wissen? Es würde eine Menge Abhängigkeiten geben. Könnten Sie nur den Header vector einfügen, ohne auch iterator oder sogar iostream einzugeben? Die STL macht dies einfach. Ein Vektor kennt den von ihm definierten Iteratortyp und das ist alles. Die STL-Algorithmen kennen nichts . Sie müssen nicht einmal einen Iterator-Header einfügen, obwohl sie alle Iteratoren als Parameter akzeptieren. Was ist dann modularer?

Die STL folgt möglicherweise nicht den Regeln von OOP, wie es Java definiert, erreicht jedoch nicht die Ziele von OOP? Erreicht es nicht Wiederverwendbarkeit, geringe Kopplung, Modularität und Kapselung?

Und erreicht es diese Ziele nicht besser als eine OOP-konforme Version?

Was den Grund betrifft, warum die STL in die Sprache übernommen wurde, sind verschiedene Dinge passiert, die zur STL geführt haben.

Zuerst wurden Vorlagen zu C++ hinzugefügt. Sie wurden aus dem gleichen Grund hinzugefügt, wie Generika zu .NET hinzugefügt wurden. Es schien eine gute Idee zu sein, Dinge wie "Container vom Typ T" zu schreiben, ohne die Typensicherheit zu vernachlässigen. Natürlich war die Implementierung, für die sie sich entschieden haben, viel komplexer und leistungsfähiger.

Dann stellten die Leute fest, dass der von ihnen hinzugefügte Vorlagenmechanismus noch leistungsfähiger war als erwartet. Und jemand fing an, mit Vorlagen zu experimentieren, um eine allgemeinere Bibliothek zu schreiben. Eine, die von der funktionalen Programmierung inspiriert ist und die alle neuen Funktionen von C++ nutzt.

Er präsentierte es dem C++ - Sprachkomitee, das eine Weile brauchte, um sich daran zu gewöhnen, weil es so seltsam und anders aussah, aber schließlich erkannte er, dass es besser funktionierte als das traditionelle OOP Äquivalente müssten sie sonst einbeziehen . Sie nahmen einige Anpassungen vor und nahmen sie in die Standardbibliothek auf.

Es war keine ideologische Entscheidung, es war keine politische Entscheidung, ob wir OOP sein wollen oder nicht, sondern eine sehr pragmatische. Sie bewerteten die Bibliothek und stellten fest, dass sie sehr gut funktionierte.

In jedem Fall sind beide Gründe, die Sie für die Begünstigung der STL anführen, unbedingt erforderlich.

Die C++ - Standardbibliothek muss effizient sein. Wenn es weniger effizient ist als beispielsweise der entsprechende handgerollte C-Code, würden die Leute es nicht verwenden. Das würde die Produktivität senken, die Wahrscheinlichkeit von Fehlern erhöhen und insgesamt nur eine schlechte Idee sein.

Und die STL hat, um mit primitiven Typen zu arbeiten, denn primitive Typen sind alles, was Sie in C haben, und sie machen einen Großteil beider Sprachen aus. Wenn die STL nicht mit nativen Arrays funktionieren würde, wäre dies nutzlos.

Ihre Frage geht stark davon aus, dass OOP "am besten" ist. Ich bin gespannt warum. Sie fragen, warum sie "die klassische OOP aufgegeben haben". Ich frage mich, warum sie daran hätten festhalten sollen. Welche Vorteile hätte es gehabt?

590
jalf

Die direkteste Antwort auf die Frage/Beschwerde lautet: Die Annahme, dass C++ eine OOP Sprache ist, ist eine falsche Annahme.

C++ ist eine Multiparadigmasprache. Es kann nach OOP Prinzipien programmiert werden, es kann prozedural programmiert werden, es kann generisch programmiert werden (Templates), und mit C++ 11 (früher bekannt als C++ 0x) können einige Dinge sogar programmiert werden funktionell.

Die Entwickler von C++ sehen dies als einen Vorteil an, und sie würden argumentieren, dass die Einschränkung von C++ das Verhalten einer reinen OOP - Sprache zur Folge hat, wenn die generische Programmierung das Problem besser und besser löst generisch wäre ein Schritt zurück.

86
Tyler McHenry

Meines Wissens ist es so, dass Stroustrup ursprünglich ein Container-Design im "OOP-Stil" bevorzugte und tatsächlich keine andere Möglichkeit sah, dies zu tun. Alexander Stepanov ist der Verantwortliche für die STL, und seine Ziele beinhalteten nicht "objektorientiert machen" :

Das ist der grundlegende Punkt: Algorithmen werden auf algebraischen Strukturen definiert. Ich habe noch ein paar Jahre gebraucht, um zu realisieren, dass man den Begriff der Struktur erweitern muss, indem man den normalen Axiomen Komplexitätsanforderungen hinzufügt. Ich glaube, dass Iteratortheorien für die Informatik genauso wichtig sind wie Theorien von Ringen oder Banachräumen für die Mathematik. Jedes Mal, wenn ich einen Algorithmus anschaue, würde ich versuchen, eine Struktur zu finden, in der er definiert ist. Ich wollte also Algorithmen generisch beschreiben. Das mache ich gerne. Ich kann einen Monat lang an einem bekannten Algorithmus arbeiten, um seine generische Darstellung zu finden. ...

Zumindest für mich ist STL die einzige Möglichkeit, wie Programmierung möglich ist. Es unterscheidet sich in der Tat erheblich von der C++ - Programmierung, wie sie präsentiert wurde und wird in den meisten Lehrbüchern immer noch präsentiert. Aber ich habe nicht versucht, in C++ zu programmieren. Ich habe versucht, den richtigen Weg zu finden, um mit Software umzugehen. ...

Ich hatte viele Fehlstarts. Zum Beispiel habe ich jahrelang versucht, etwas für Vererbung und virtuelle Verwendungen zu finden, bevor ich begriff, warum dieser Mechanismus grundlegend fehlerhaft war und nicht verwendet werden sollte. Ich bin sehr glücklich, dass niemand alle Zwischenschritte sehen konnte - die meisten waren sehr dumm.

(Er erklärt, warum Vererbung und virtuelle Objekte - a.k.a. objektorientiertes Design "grundsätzlich fehlerhaft waren und im restlichen Interview nicht verwendet werden sollten).

Nachdem Stepanov seine Bibliothek Stroustrup vorgestellt hatte, unternahmen Stroustrup und andere herkulische Bemühungen, um sie in den ISO-C++ - Standard zu bringen (gleiches Interview):

Die Unterstützung von Bjarne Stroustrup war entscheidend. Bjarne wollte wirklich STL im Standard und wenn Bjarne etwas will, bekommt er es. Er hat mich sogar gezwungen, Änderungen an STL vorzunehmen, die ich für niemanden sonst machen würde ... er ist der aufgeschlossenste Mensch, den ich kenne. Er lässt Dinge erledigen. Er brauchte eine Weile, um zu verstehen, worum es bei STL ging, aber als er es tat, war er bereit, es durchzusetzen. Er trug auch zu STL bei, indem er für die Ansicht eintrat, dass mehr als eine Art der Programmierung gültig war - mehr als ein Jahrzehnt lang ohne Ende und Hype und die Kombination von Flexibilität, Effizienz, Überlastung und Typensicherheit Vorlagen, die STL möglich machten. Ich möchte ganz klar sagen, dass Bjarne der herausragende Sprachdesigner meiner Generation ist. 

67
Max Lybbert

Die Antwort findet sich in diesem Interview mit Stepanov, dem Autor der STL:

Ja. STL ist nicht objektorientiert. ICH denke, dass Objektorientierung .__ ist. fast so viel von einem Schwindel wie künstlich Intelligenz. Ich habe noch keine interessantes Stück Code, das kommt von diesen OO Menschen.

23
StackedCrooked

Warum wäre ein reiner OOP - Entwurf für eine Datenstruktur- und Algorithmenbibliothek besser?! OOP ist nicht die Lösung für alles.

IMHO, STL ist die eleganteste Bibliothek, die ich je gesehen habe :)

für deine Frage

sie brauchen keinen Laufzeit-Polymorphismus, es ist ein Vorteil für STL, die Library tatsächlich mit statischem Polymorphismus zu implementieren, das heißt Effizienz. Schreiben Sie eine generische Sortierung oder Distanz oder einen beliebigen Algorithmus, der auf ALLE Container anwendbar ist! .your Sort in Java würde Funktionen aufrufen, die über n-Ebenen dynamisch ausgeführt werden können!

Du brauchst blöde Dinge wie Boxen und Unboxing, um schlechte Annahmen der sogenannten Pure OOP -Sprachen zu verbergen.

Das einzige Problem, das ich mit STL und Vorlagen im Allgemeinen sehe, sind die schrecklichen Fehlermeldungen ..., die mit Concepts in C++ 0X gelöst werden.

Vergleichen von STL mit Collections in Java ist vergleichbar mit Taj Mahal mit meinem Haus :) 

17
AraK

vorlagen-Typen sollten einem "Konzept" (Eingabe-Iterator, Vorwärts-Iterator usw.) folgen, wobei die tatsächlichen Details des Konzepts ausschließlich durch die Implementierung der Vorlagenfunktion/-klasse und nicht durch die Klasse des Typs definiert werden Wird mit der Vorlage verwendet, die ein wenig gegen die Verwendung von OOP gerichtet ist.

Ich denke, Sie missverstehen die beabsichtigte Verwendung von Konzepten durch Vorlagen. Forward Iterator ist beispielsweise ein sehr genau definiertes Konzept. Um die Ausdrücke zu finden, die gültig sein müssen, damit eine Klasse ein Forward Iterator ist, und ihre Semantik einschließlich der Komplexität der Berechnungen, lesen Sie den Standard oder http://www.sgi.com/tech/stl /ForwardIterator.html (Sie müssen den Links zu Input, Output und Trivial Iterator folgen, um alles zu sehen).

Dieses Dokument ist eine vollkommen gute Schnittstelle, und "die tatsächlichen Details des Konzepts" werden genau dort definiert. Sie werden nicht von den Implementierungen von Forward-Iteratoren definiert, und sie werden auch nicht von den Algorithmen definiert, die Forward-Iteratoren verwenden.

Es gibt drei Unterschiede in der Handhabung von Schnittstellen zwischen STL und Java:

1) STL definiert gültige Ausdrücke unter Verwendung des Objekts, wohingegen Java Methoden definiert, die für das Objekt aufrufbar sein müssen. Natürlich kann ein gültiger Ausdruck ein Methodenaufruf (Member-Funktion) sein, muss es aber nicht.

2) Java - Schnittstellen sind Laufzeitobjekte, während STL-Konzepte auch mit RTTI zur Laufzeit nicht sichtbar sind.

3) Wenn Sie die erforderlichen gültigen Ausdrücke für ein AWL-Konzept nicht gültig machen, wird beim Instanziieren einer Vorlage mit dem Typ ein nicht angegebener Kompilierungsfehler angezeigt. Wenn Sie eine erforderliche Methode einer Java - Schnittstelle nicht implementieren können, wird ein bestimmter Kompilierungsfehler angezeigt.

Dieser dritte Teil ist, wenn Sie eine Art (zur Kompilierungszeit) "Enten-Typisierung" mögen: Schnittstellen können implizit sein. In Java sind Interfaces etwas explizit: Eine Klasse "is" Iterable nur dann, wenn sie sagt , dass sie Iterable implementiert. Der Compiler kann überprüfen, ob alle Signaturen seiner Methoden vorhanden und korrekt sind, aber die Semantik ist immer noch implizit (d. H. Sie sind entweder dokumentiert oder nicht, aber nur mehr Code (Komponententests) kann Ihnen sagen, ob die Implementierung korrekt ist).

In C++ sind wie in Python sowohl Semantik als auch Syntax implizit, obwohl Sie in C++ (und in Python, wenn Sie den Präprozessor mit starker Typisierung verwenden) Hilfe vom Compiler erhalten. Wenn ein Programmierer eine Java-ähnliche explizite Deklaration von Schnittstellen durch die implementierende Klasse benötigt, besteht der Standardansatz darin, Typmerkmale zu verwenden (und eine mehrfache Vererbung kann verhindern, dass dies zu ausführlich ist). Was im Vergleich zu Java fehlt, ist eine einzige Vorlage, die ich mit meinem Typ instanziieren kann und die nur dann kompiliert wird, wenn alle erforderlichen Ausdrücke für meinen Typ gültig sind. Dies würde mir sagen, ob ich alle erforderlichen Bits implementiert habe, "bevor ich sie verwende". Das ist eine Annehmlichkeit, aber nicht der Kern von OOP (und es testet immer noch keine Semantik, und Code zum Testen der Semantik würde natürlich auch die Gültigkeit der fraglichen Ausdrücke testen).

STL kann für Ihren Geschmack ausreichend OO sein oder auch nicht, trennt jedoch die Schnittstelle sauber von der Implementierung. Es fehlt Java die Fähigkeit, über Schnittstellen nachzudenken, und es meldet Verstöße gegen Schnittstellenanforderungen unterschiedlich.

sie können der Funktion mitteilen, dass ... ein Forward-Iterator nur anhand seiner Definition erwartet wird, für die Sie entweder die Implementierung oder die Dokumentation anzeigen müssen ...

Persönlich denke ich, dass implizite Typen eine Stärke sind, wenn sie richtig eingesetzt werden. Der Algorithmus sagt, was er mit seinen Template-Parametern macht, und der Implementierer stellt sicher, dass diese Dinge funktionieren: Es ist genau der gemeinsame Nenner dessen, was "Interfaces" tun sollen. Außerdem ist es mit STL unwahrscheinlich, dass Sie std::copy verwenden, wenn Sie die Forward-Deklaration in einer Header-Datei finden. Programmierer sollten anhand ihrer Dokumentation herausfinden, was eine Funktion bewirkt, und nicht nur anhand der Funktionssignatur. Dies gilt für C++, Python oder Java. Es gibt Einschränkungen, was mit dem Tippen in einer beliebigen Sprache erreicht werden kann, und der Versuch, mit dem Tippen etwas zu tun, was es nicht tut (Semantik prüfen), wäre ein Fehler.

Allerdings benennen STL-Algorithmen ihre Template-Parameter normalerweise so, dass klar wird, welches Konzept erforderlich ist. Dies dient jedoch dazu, in der ersten Zeile der Dokumentation nützliche zusätzliche Informationen bereitzustellen, und nicht, um zukunftsgerichtete Erklärungen aussagekräftiger zu machen. Es gibt mehr Dinge, die Sie wissen müssen, als in den Parametertypen gekapselt werden können, daher müssen Sie die Dokumente lesen. (Bei Algorithmen, die einen Eingabebereich und einen Ausgabe-Iterator verwenden, benötigt der Ausgabe-Iterator möglicherweise genügend "Platz" für eine bestimmte Anzahl von Ausgaben, basierend auf der Größe des Eingabebereichs und möglicherweise den darin enthaltenen Werten. Geben Sie dies möglichst genau ein. )

Hier ist Bjarne zu explizit deklarierten Schnittstellen: http://www.artima.com/cppsource/cpp0xP.html

In Generics muss ein Argument aus einer Klasse stammen, die von einer Schnittstelle abgeleitet ist (das C++ - Äquivalent zur Schnittstelle ist eine abstrakte Klasse), die in der Definition der generischen Klasse angegeben ist. Das bedeutet, dass alle generischen Argumenttypen in eine Hierarchie passen müssen. Dies bedingt unnötige Einschränkungen für das Design und erfordert unangemessene Voraussicht seitens der Entwickler. Wenn Sie beispielsweise ein generisches Element schreiben und ich eine Klasse definiere, können die Benutzer meine Klasse nicht als Argument für Ihr generisches Element verwenden, es sei denn, ich kenne die von Ihnen angegebene Schnittstelle und habe meine Klasse davon abgeleitet. Das ist starr.

Wenn Sie es anders herum betrachten, können Sie mit Duck Typing eine Schnittstelle implementieren, ohne zu wissen, dass die Schnittstelle vorhanden ist. Oder jemand kann eine Schnittstelle absichtlich so schreiben, dass Ihre Klasse sie implementiert, indem er Ihre Dokumente konsultiert, um festzustellen, dass er nicht nach etwas fragt, was Sie noch nicht getan haben. Das ist flexibel.

11
Steve Jessop

"OOP bedeutet für mich nur Messaging, lokale Aufbewahrung und Schutz und Verstecken von Statusprozessen sowie extrem spätes Binden aller Dinge. Dies kann in Smalltalk und in LISP erfolgen. Möglicherweise gibt es andere Systeme, in denen dies möglich ist Ich bin mir ihrer nicht bewusst. " - Alan Kay, Schöpfer von Smalltalk.

C++, Java und die meisten anderen Sprachen sind alles andere als klassische OOP. Das Argument für Ideologien ist jedoch nicht besonders produktiv. C++ ist in keiner Weise rein, sondern implementiert Funktionen, die zu diesem Zeitpunkt pragmatisch erscheinen.

8
Ben Hughes

STL begann mit der Absicht, eine große Bibliothek bereitzustellen, die die am häufigsten verwendeten Algorithmen abdeckt - mit dem Ziel konsistentes Verhalten und performance. Das Template war ein Schlüsselfaktor, um die Implementierung und das Ziel realisierbar zu machen.

Um nur eine weitere Referenz anzugeben:

Al Stevens Interviews Alex Stepanov, im März 1995 von DDJ:

Stepanov erläuterte seine Arbeitserfahrung und seine Entscheidung für eine große Algorithmusbibliothek, die sich schließlich zu STL entwickelte.

Erzählen Sie uns etwas über Ihr langfristiges Interesse an generischer Programmierung

..... Dann wurde mir eine Stelle bei Bell Laboratories angeboten, die in der C++ - Gruppe für C++ - Bibliotheken arbeitete. Sie fragten mich, ob ich das in C++ machen könnte. Natürlich kannte ich C++ nicht und natürlich sagte ich, ich könnte. Aber ich konnte es nicht in C++ machen, denn 1987 hatte C++ keine Vorlagen, die für diesen Programmierstil unerlässlich sind. Vererbung war der einzige Mechanismus, um Generizität zu erreichen, und es war nicht ausreichend.

Selbst jetzt ist die C++ - Vererbung für die generische Programmierung nicht von großem Nutzen. Besprechen wir warum. Viele Leute haben versucht, Vererbung zu verwenden, um Datenstrukturen und Containerklassen zu implementieren. Wie wir jetzt wissen, gab es wenige oder gar keine erfolgreichen Versuche. Die C++ - Vererbung und der damit verbundene Programmierstil sind drastisch eingeschränkt. Es ist unmöglich, ein Design zu implementieren, das eine so unbedeutende Sache wie die Gleichheit beinhaltet. Wenn Sie mit einer Basisklasse X am Stamm der Hierarchie beginnen und einen virtuellen Gleichheitsoperator für diese Klasse definieren, der ein Argument des Typs X verwendet, leiten Sie die Klasse Y von der Klasse X ab. Was ist die Schnittstelle der Gleichheit? Es hat Gleichheit, die Y mit X vergleicht. Am Beispiel von Tieren (OO-Leute lieben Tiere) definieren Sie das Säugetier und leiten Giraffe vom Säugetier ab. Definieren Sie dann eine Elementfunktion Mate, bei der sich Tier mit Tier paart und ein Tier zurückgibt. Dann leiten Sie Giraffen vom Tier ab und natürlich hat es eine Funktion, wo Giraffe sich mit Tier paart und ein Tier zurückgibt. Es ist definitiv nicht das, was du willst. Die Paarung ist zwar für C++ - Programmierer nicht sehr wichtig, die Gleichheit jedoch. Ich kenne keinen einzigen Algorithmus, bei dem Gleichheit irgendeiner Art nicht verwendet wird.

6
yowkee

Das grundlegende Problem mit 

void MyFunc(ForwardIterator *I);

wie erhält man sicher die Art der Sache, die der Iterator zurückgibt? Mit Vorlagen wird dies zur Kompilierzeit erledigt.

5
anon

Für einen Moment betrachten wir die Standardbibliothek als eine Datenbank mit Sammlungen und Algorithmen.

Wenn Sie die Geschichte von Datenbanken studiert haben, wissen Sie zweifellos, dass Datenbanken zu Beginn meist "hierarchisch" waren. Hierarchische Datenbanken entsprachen sehr genau der klassischen OOP - insbesondere der Single-Vererbungsvariante, wie sie von Smalltalk verwendet wird.

Im Laufe der Zeit wurde klar, dass mit hierarchischen Datenbanken fast alles modelliert werden kann, aber In einigen Fällen war das Einzelvererbungsmodell ziemlich einschränkend. Wenn Sie eine Holztür hatten, war es praktisch, sie entweder als Tür oder als Stück Rohmaterial (Stahl, Holz usw.) betrachten zu können.

Also erfanden sie Netzwerkmodelldatenbanken. Netzwerkmodelldatenbanken entsprechen sehr genau der Mehrfachvererbung. C++ unterstützt die Mehrfachvererbung vollständig, während Java eine eingeschränkte Form unterstützt (Sie können nur von einer Klasse erben, aber auch beliebig viele Schnittstellen implementieren).

Sowohl das hierarchische Modell als auch die Netzwerkmodelldatenbanken sind im Allgemeinen vom allgemeinen Gebrauch verschwunden (obwohl einige in ziemlich spezifischen Nischen bleiben). Für die meisten Zwecke wurden sie durch relationale Datenbanken ersetzt.

Grund für die Übernahme relationaler Datenbanken war häufig die Vielseitigkeit. Das relationale Modell ist funktional eine Obermenge des Netzwerkmodells (die wiederum eine Obermenge des hierarchischen Modells ist).

C++ ist weitgehend demselben Pfad gefolgt. Die Entsprechung zwischen Einzelvererbung und dem hierarchischen Modell sowie zwischen Mehrfachvererbung und dem Netzwerkmodell ist ziemlich offensichtlich. Die Entsprechung zwischen C++ - Vorlagen und dem hierarchischen Modell mag weniger offensichtlich sein, ist aber trotzdem ziemlich genau.

Ich habe keinen formalen Beweis dafür gesehen, aber ich glaube, die Fähigkeiten von Vorlagen sind eine Obermenge von denen, die durch Mehrfachvererbung bereitgestellt werden (was eindeutig eine Obermenge von Einzelvererbung ist). Der eine schwierige Teil ist, dass Vorlagen meist statisch gebunden sind. Das heißt, die gesamte Bindung erfolgt zur Kompilierzeit und nicht zur Laufzeit. Ein formeller Beweis dafür, dass die Vererbung eine Übermenge der Vererbungsmöglichkeiten bietet, kann durchaus schwierig und komplex sein (oder sogar unmöglich sein).

In jedem Fall denke ich, dass dies der wahre Grund ist, warum C++ keine Vererbung für seine Container verwendet. Es gibt keinen wirklichen Grund, dies zu tun, da Vererbung nur einen Teil der von Vorlagen bereitgestellten Funktionen bereitstellt. Da Vorlagen in einigen Fällen grundsätzlich notwendig sind, können sie auch fast überall verwendet werden.

2
Jerry Coffin

Wie macht man Vergleiche mit ForwardIterator *? Wie können Sie also überprüfen, ob der von Ihnen gesuchte Artikel das ist, wonach Sie suchen oder ob Sie ihn übergeben haben?

Die meiste Zeit würde ich so etwas verwenden:

void MyFunc(ForwardIterator<MyType>& i)

was bedeutet, dass ich weiß, dass ich auf MyTypes zeige, und ich weiß, wie man diese vergleicht. Obwohl es wie eine Vorlage aussieht, ist es nicht wirklich (kein Schlüsselwort "Vorlage").

0
Tanktalus

Diese Frage hat viele großartige Antworten. Es sollte auch erwähnt werden, dass Vorlagen ein offenes Design unterstützen. Beim aktuellen Stand objektorientierter Programmiersprachen muss bei solchen Problemen das Besuchermuster verwendet werden, und true OOP sollte mehrere dynamische Bindungen unterstützen. Siehe Open Multi-Methods für C++, P. Pirkelbauer, et al. für sehr interessante Lektüre.

Ein weiterer interessanter Punkt von Vorlagen ist, dass sie auch für Laufzeitpolymorphismus verwendet werden können. Zum Beispiel

template<class Value,class T>
Value euler_fwd(size_t N,double t_0,double t_end,Value y_0,const T& func)
    {
    auto dt=(t_end-t_0)/N;
    for(size_t k=0;k<N;++k)
        {y_0+=func(t_0 + k*dt,y_0)*dt;}
    return y_0;
    }

Beachten Sie, dass diese Funktion auch funktioniert, wenn Value ein Vektor irgendeiner Art ist (not std :: vector, der std::dynamic_array genannt werden sollte, um Verwirrung zu vermeiden).

Wenn func klein ist, wird diese Funktion durch Inlining erheblich verbessert. Verwendungsbeispiel

auto result=euler_fwd(10000,0.0,1.0,1.0,[](double x,double y)
    {return y;});

In diesem Fall sollten Sie die genaue Antwort kennen (2.718 ...), aber es ist leicht, eine einfache ODE ohne Elementarlösung zu erstellen (Hinweis: Verwenden Sie ein Polynom in y).

Jetzt haben Sie einen großen Ausdruck in func und verwenden den ODE-Solver an vielen Stellen, sodass Ihre ausführbare Datei überall mit Template-Instantiierungen verschmutzt wird. Was ist zu tun? Das erste, was zu beachten ist, ist, dass ein regulärer Funktionszeiger funktioniert. Dann möchten Sie Currying hinzufügen, damit Sie eine Schnittstelle und eine explizite Instantiierung schreiben

class OdeFunction
    {
    public:
        virtual double operator()(double t,double y) const=0;
    };

template
double euler_fwd(size_t N,double t_0,double t_end,double y_0,const OdeFunction& func);

Die obige Instantiierung funktioniert jedoch nur für double. Warum schreiben Sie die Schnittstelle nicht als Vorlage:

template<class Value=double>
class OdeFunction
    {
    public:
        virtual Value operator()(double t,const Value& y) const=0;
    };

und spezialisieren Sie sich auf einige gängige Werttypen:

template double euler_fwd(size_t N,double t_0,double t_end,double y_0,const OdeFunction<double>& func);

template vec4_t<double> euler_fwd(size_t N,double t_0,double t_end,vec4_t<double> y_0,const OdeFunction< vec4_t<double> >& func); // (Native AVX vector with four components)

template vec8_t<float> euler_fwd(size_t N,double t_0,double t_end,vec8_t<float> y_0,const OdeFunction< vec8_t<float> >& func); // (Native AVX vector with 8 components)

template Vector<double> euler_fwd(size_t N,double t_0,double t_end,Vector<double> y_0,const OdeFunction< Vector<double> >& func); // (A N-dimensional real vector, *not* `std::vector`, see above)

Wenn die Funktion zuerst um eine Schnittstelle herum entworfen worden wäre, dann wären Sie gezwungen gewesen, von diesem ABC zu erben. Jetzt haben Sie diese Option sowie Funktionszeiger, Lambda oder ein beliebiges anderes Funktionsobjekt. Der Schlüssel hier ist, dass wir operator()() haben müssen, und wir müssen einige arithmetische Operatoren für ihren Rückgabetyp verwenden können. Die Vorlagenmaschinerie würde in diesem Fall also kaputt gehen, wenn C++ keine Überlastung durch den Operator hatte.

0
user877329