Warum wurden Konzepte (generische Programmierung) konzipiert, als wir bereits Klassen und Schnittstellen hatten?


8

Auch auf stackoverflow.com :

Ich verstehe, dass STL-Konzepte existieren mussten und dass es dumm wäre, sie "Klassen" oder "Schnittstellen" zu nennen, wenn sie tatsächlich nur dokumentierte (menschliche) Konzepte sind und zu diesem Zeitpunkt nicht in C ++ - Code übersetzt werden konnten. Aber als sie die Möglichkeit hatten, die Sprache zu erweitern, um Konzepte aufzunehmen, warum haben sie nicht einfach die Fähigkeiten von Klassen geändert und / oder Schnittstellen eingeführt?

Ist ein Konzept einer Schnittstelle nicht sehr ähnlich (100% abstrakte Klasse ohne Daten)? Wenn ich es mir anschaue, scheint es mir, dass Schnittstellen nur keine Unterstützung für Axiome bieten, aber vielleicht könnten Axiome in die Schnittstellen von C ++ eingeführt werden (unter Berücksichtigung einer hypothetischen Übernahme von Schnittstellen in C ++, um Konzepte zu übernehmen), nicht wahr? Ich denke, sogar automatische Konzepte könnten leicht zu einer solchen C ++ - Schnittstelle hinzugefügt werden (automatische Schnittstelle LessThanComparable, irgendjemand?).

Ist eine concept_map dem Adaptermuster nicht sehr ähnlich? Wenn alle Methoden inline sind, existiert der Adapter im Wesentlichen nicht über die Kompilierungszeit hinaus. Der Compiler ersetzt einfach Aufrufe der Schnittstelle durch die Inline-Versionen und ruft das Zielobjekt direkt zur Laufzeit auf.

Ich habe von etwas gehört, das als statische objektorientierte Programmierung bezeichnet wird. Dies bedeutet im Wesentlichen, dass die Konzepte der Objektorientierung in der generischen Programmierung effektiv wiederverwendet werden, wodurch der größte Teil der Leistung von OOP genutzt werden kann, ohne dass ein Ausführungsaufwand entsteht. Warum wurde diese Idee nicht weiter berücksichtigt?

Ich hoffe das ist klar genug. Ich kann das umschreiben, wenn Sie denken, dass ich es nicht war; Lass es mich wissen.

Antworten:


13

Kurze Antwort: Sie mischen Konzepte vor der Kompilierung und Konzepte zur Kompilierungszeit , deren Zweck Ähnlichkeiten aufweist. Schnittstellen (abstrakte Klassen und die gesamte Implementierung des Objektorientierungsparadigmas) werden zur Kompilierungszeit erzwungen . Konzepte sind die gleiche Idee, aber im Kontext der generischen Programmierung, die in C ++ VOR der Kompilierungszeit auftritt . Wir haben das letzte Feature noch nicht.

Aber lassen Sie mich von Anfang an erklären.


Lange Antwort:

Tatsächlich sind Konzepte nur eine Sprachverbesserung und "für den Programmierer einfacher" für etwas, das bereits in der Sprache vorhanden ist und das man als "Ententypisierung" bezeichnen könnte.

Wenn Sie einen Typ an eine Vorlagenfunktion übergeben, dh eine generische Funktion, aus der der Compiler beim Aufruf echten (Inline-) Code generiert, muss dieser Typ einige Eigenschaften (Merkmale?) Haben, die im Vorlagencode verwendet werden. Es ist also die Idee, Enten zu tippen, ABER alles wird zur Kompilierungszeit generiert und ausgeführt .

Was passiert, wenn der Typ nicht die erforderlichen Eigenschaften hat?

Nun, der Compiler wird wissen, dass es erst dann ein Problem gibt, wenn der aus der Vorlage generierte Code kompiliert wurde und fehlschlägt. Das bedeutet, dass der Fehler, der generiert wird, ein Fehler im Vorlagencode ist, der dem Programmierer als sein Fehler angezeigt wird. Außerdem enthält der Fehler aufgrund der Metainformationen, die bei der Generierung von Vorlagencode bereitgestellt werden, unzählige Informationen, um zu wissen, um welche Instanziierung der Vorlage es sich handelt.

Einige Probleme damit: Erstens ist Vorlagencode meistens Bibliothekscode, und die meisten Programmierer sind Benutzer von Bibliothekscode, keine Verfasser von Bibliothekscode. Das bedeutet, dass diese Art von kryptischem Fehler wirklich schwer zu verstehen ist, wenn Sie nicht verstehen, wie die Bibliothek geschrieben ist (nicht nur das Design, wie sie wirklich implementiert ist). Das zweite Problem ist, dass selbst wenn der Programmierer den Vorlagencode geschrieben hat, die Fehlerursachen möglicherweise immer noch unklar sind, da der Compiler erkennen kann, dass ein Problem zu spät vorliegt: Wenn der generierte Code kompiliert wird. Wenn das Problem mit den relativen Typ Eigenschaften, dann sollte es sie prüft , noch bevor Sie den Code zu generieren.

Dies ist es, was Konzepte zulassen (und für die sie entwickelt wurden): Damit der (generische Code-) Programmierer die Eigenschaften von Typen angeben kann, die als Vorlagenparameter übergeben werden, und der Compiler dann explizite Fehler angeben kann, falls die bereitgestellten Typen die Anforderungen nicht erfüllen Anforderungen.

Sobald die Prüfung erfolgreich ist, wird der Code aus der Vorlage generiert und dann mit Sicherheit erfolgreich kompiliert.

Die gesamte Konzeptprüfung erfolgt ausschließlich vor der Kompilierungszeit . Es überprüft die Typen selbst und nicht die Objekttypen . Vor der Kompilierungszeit ist kein Objekt vorhanden.

Nun zu "Schnittstellen".

Wenn Sie einen abstrakten oder virtuellen Basistyp erstellen, erlauben Sie Code, damit Objekte der untergeordneten Typen zu bearbeiten, ohne deren tatsächliche Implementierung zu kennen. Um dies zu erzwingen, macht der Basistyp Mitglieder verfügbar, die virtuell sind und möglicherweise von den untergeordneten Typen überlastet werden (oder müssen).

Dies bedeutet, dass der Compiler zur Kompilierungszeit überprüfen kann, ob alle Objekte, die an eine Funktion übergeben werden, die einen Verweis auf die Basisklasse erfordert, 1. einem der untergeordneten Typen der Basisklasse angehören müssen, 2. dass der untergeordnete Typ Implementierungen von haben muss ggf. in Basisklassen deklarierte virtuelle reine Funktionen.

So zum Zeitpunkt der Kompilierung , prüft der Compiler die Schnittstellen des Objekts Typen und Bericht erstellen , wenn etwas fehlt.

Es ist die gleiche Idee wie bei Concepts, aber es kommt zu spät , wie in der Concept-Beschreibung angegeben. Es tritt zur Kompilierungszeit auf. Wir befinden uns nicht im generischen Code (Vorlagencode), sondern erst, nachdem er verarbeitet wurde, und es ist bereits zu spät, um zu überprüfen, ob die Typen generische Anforderungen erfüllen, die von virtuellen Basisklassen nicht verfügbar gemacht werden können. Tatsächlich existiert die gesamte Implementierung des Objektorientierungsparadigmas in C ++ nicht einmal, wenn der Vorlagencode verarbeitet wird. Es gibt (noch) keine Objekte. Das ist

Klassen beschreiben Einschränkungen für Objekte, die zum Überprüfen der Anforderungen für Funktionen verwendet werden sollen, die diese Objekte bearbeiten. Konzepte beschreiben Einschränkungen für Typen (einschließlich Klassen), die zum Überprüfen der Anforderungen für generischen Code verwendet werden sollen, um aus diesen Typen und der Kombination aus generischem Code echten Code zu generieren.

Es handelt sich also wieder um dieselbe "Überprüfung der geistigen Gesundheit", jedoch in einer anderen Sprachebene, dh um Vorlagen. Vorlagen sind eine vollständige (vollständig) Sprache, die Metaprogrammierung und Programmiertypen ermöglicht, noch bevor sie im kompilierten Code erscheinen. Es ist ein bisschen wie das Schreiben von Skripten für den Compiler. Angenommen, Sie können ein Skript erstellen. Klassen sind nur vom Skript manipulierte Werte. Derzeit gibt es keine andere Möglichkeit, Einschränkungen für diese Werte zu überprüfen, als das Skript auf nicht offensichtliche Weise zum Absturz zu bringen. Konzepte sind genau das: Geben Sie die Eingabe für diese Werte ein (die im generierten Code Typen sind). Ich bin mir nicht sicher, ob ich klar bin ...

Ein weiterer wirklich wichtiger Unterschied zwischen virtuellen Basisklassen und Konzepten besteht darin, dass die erste eine starke Beziehung zwischen Typen erzwingt und sie "blutgebunden" macht. Während die Metaprogrammierung von Vorlagen das "Enten-Tippen" ermöglicht, erlauben Konzepte nur, die Anforderungen klarer zu machen.


4

Das Deklarieren einer Klasse ist " Programmieren von Objekten ".
Ein Konzept zu deklarieren ist " Programmierklassen ".

Natürlich, da immer Programmierung ist, können bestimmte Analogien gesehen werden, aber die beiden Dinge gehören zu einer unterschiedlichen Phase des Abstraktionsprozesses. Grundsätzlich teilt eine "Klasse" (und alles ist um sie herum, wie "Schnittstellen") dem Compiler mit, wie die Objekte strukturiert werden sollen, die der Executor-Computer zur Laufzeit instanziiert. Ein "Konzept" soll dem Compiler mitteilen, wie eine "Klasse" so strukturiert sein soll, dass sie in einem bestimmten Kontext "kompilierbar" ist.

Natürlich ist es theoretisch möglich, diese Schritte immer wieder zu wiederholen

  • Objekte
  • Arten von Objekten ( Klassen )
  • Arten von (Arten von Objekten) ( Konzepte )
  • Arten von (Arten von (Arten von Objekten)) ( ??? )
  • ..... .....

Im Moment wurden "Konzepte" durch die C ++ 0x-Spezifikationen gestrichen (da sie noch einige Arbeit erfordern und beibehalten wurden, um nicht länger zu verzögern). Die Idee des Konzepts n Ich weiß nicht, ob kann immer nützlich sein.


Das macht konzeptionell sehr viel Sinn; Ich versuche es einzusinken und zu überlegen, ob dies die Frage beantwortet. Vielen Dank.
Gui Prá

3

Die einfache Antwort auf praktisch alle Ihre Fragen lautet: "Weil C ++ - Compiler scheiße sind". Ernsthaft. Sie basieren auf der Translation Unit-Technologie von C, die viele nützliche Dinge effektiv verbietet, und die vorhandenen Vorlagenimplementierungen sind so schrecklich langsam wie sie sind. Konzepte wurden aus konzeptionellen Gründen nicht gekürzt - sie wurden gekürzt, weil es keine zuverlässige Implementierung gab, ConceptGCC extrem langsam war und das Spezifizieren von Konzepten absurd lange dauerte. Herb Sutter gab an, dass die Angabe der in der Standardbibliothek verwendeten Konzepte mehr Platz beanspruchte als die Angabe der gesamten Vorlagen.

Wahrscheinlich zwischen SFINAE decltypeund static_assert, sie sind größtenteils implementierbar, wie es jetzt sowieso ist.


2

Nach meinem Verständnis haben Schnittstellen und Konzepte in verschiedenen Teilen der C ++ - Sprache ähnliche Zwecke.

Wie in der Antwort auf die ursprüngliche Frage erwähnt: Die Implementierung einer Schnittstelle wird vom Implementierer einer Klasse zur Entwurfszeit entschieden. Sobald eine Klasse veröffentlicht wurde, kann sie nur die Schnittstellen unterstützen, von denen sie zur Entwurfszeit abgeleitet wurde.

Zwei unterschiedliche Schnittstellen mit genau denselben Elementfunktionen und Semantik (dh demselben Konzept) sind weiterhin zwei unterschiedliche Schnittstellen. Wenn Sie die Semantik beider Schnittstellen unterstützen möchten, müssen Sie die Unterstützung möglicherweise zweimal implementieren.

Das ist das Problem, das die generische Programmierung umgehen soll. In der generischen C ++ - Programmierung muss der an eine Vorlage übergebene Typ lediglich die Schnittstelle (nicht großgeschrieben im Sinne der "Programmierschnittstelle" eines Typs) unterstützen, die von der Vorlage verwendet wird. Die zwei unterschiedlichen Schnittstellen mit denselben Elementfunktionen stimmen überein, und Sie müssen den Code nur einmal schreiben. Darüber hinaus funktionieren alle Typen (auch ohne explizite Schnittstellen), die dieselbe Schnittstelle unterstützen.

Dies führt zu einem zweiten Problem: Was ist, wenn Sie zwei Typen mit überlappenden Schnittstellen, aber unterschiedlicher Semantik haben? Generische Programmierung wird den Unterschied nicht erkennen können und alles wird gut kompiliert, aber das Laufzeitergebnis wird überraschend und wahrscheinlich falsch sein.

Hier kommen Konzepte ins Spiel. Wenn Sie stark vereinfachen, können Sie ein Konzept als generische (Vorlagen-) Version einer Schnittstelle betrachten. Es muss nur einmal implementiert werden, um auf eine große Anzahl potenzieller Typen angewendet zu werden, die aus dem Konzept "abgeleitet" werden können. Ein Konzept ist eine vorgegebene semantische Schnittstelle eines (Klassen-) Typs, die nicht nur auf diesen Typ beschränkt ist. Es unterscheidet sich von einer Schnittstelle darin, dass zwei sehr unterschiedliche Typen (für den generischen Compiler) immer noch dasselbe Konzept haben können, ohne auf Einschränkungen der Basisklasse oder auf Schnittstellen der Basisklasse zurückgreifen zu müssen, für die keine vom Compiler sichtbare Semantik vorliegt eigene, werden aber nur zur Typunterscheidung verwendet.


Warten Sie ... Sie sagten, Konzepte werden einmal implementiert und gelten für eine große Anzahl potenzieller Typen, die daraus "abgeleitet" werden können. Das heißt, alle potenziellen Typen müssen daraus abgeleitet werden. Wenn zwei Konzepte mit demselben Inhalt in zwei verschiedenen Bibliotheken ohne automatische Zuordnung vorhanden sind, tritt das gleiche Problem wie bei Schnittstellen auf. Gleichzeitig könnte eine Schnittstelle eine automatische Zuordnung bieten. Die Probleme sehen für mich ein und dasselbe aus.
Gui Prá

1
@ n2liquid - Konzepte sind eine andere Mischung aus Vor- und Nachteilen zwischen Schnittstellen und reiner generischer Programmierung. Sie sind keine strikte Verbesserung. Konzepte vermeiden nicht den "vorbestimmten" Teil von Schnittstellen. Konzepte vermeiden die Situation, in der Klassentypen dieselbe Semantik unterstützen, aber nicht von derselben Schnittstelle abgeleitet werden können (z. B. wenn die Schnittstelle für den Subtyp double definiert ist, während die generische Version für alle numerischen Typen gilt).
Joris Timmermans
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.