Wie richte ich eine Klasse ein, die eine Schnittstelle darstellt? Ist das nur eine abstrakte Basisklasse?
c++
inheritance
interface
abstract-class
pure-virtual
Aaron Fischer
quelle
quelle
Antworten:
Um die Antwort von bradtgmurray zu erweitern , möchten Sie möglicherweise eine Ausnahme von der Liste der reinen virtuellen Methoden Ihrer Schnittstelle machen, indem Sie einen virtuellen Destruktor hinzufügen. Auf diese Weise können Sie den Zeigerbesitz an eine andere Partei übergeben, ohne die konkrete abgeleitete Klasse verfügbar zu machen. Der Destruktor muss nichts tun, da die Schnittstelle keine konkreten Elemente enthält. Es mag widersprüchlich erscheinen, eine Funktion sowohl als virtuell als auch als Inline zu definieren, aber vertrauen Sie mir - das ist es nicht.
Sie müssen keinen Body für den virtuellen Destruktor einfügen. Es stellt sich heraus, dass einige Compiler Probleme haben, einen leeren Destruktor zu optimieren, und Sie sollten die Standardeinstellung verwenden.
quelle
=0
) Destruktor mit einem Körper zu definieren . Der Vorteil hierbei ist, dass der Compiler theoretisch sehen kann, dass vtable jetzt keine gültigen Mitglieder hat, und es insgesamt verwerfen kann. Bei einem virtuellen Destruktor mit einem Körper kann der Destruktor (virtuell) aufgerufen werden, z. B. mitten in der Konstruktion über einenthis
Zeiger (wenn das konstruierte Objekt noch vomParent
Typ ist), und daher muss der Compiler eine gültige vtable bereitstellen. Wenn Sie alsothis
während der Erstellung nicht explizit virtuelle Destruktoren über aufrufen :), können Sie Codegröße sparen.override
Schlüsselwort angeben können, um Argumente zur Kompilierungszeit und zur Überprüfung des Rückgabewerttyps zu ermöglichen. Zum Beispiel in der Erklärung von Kindvirtual void OverrideMe() override;
Erstellen Sie eine Klasse mit rein virtuellen Methoden. Verwenden Sie die Schnittstelle, indem Sie eine andere Klasse erstellen, die diese virtuellen Methoden überschreibt.
Eine rein virtuelle Methode ist eine Klassenmethode, die als virtuell definiert und 0 zugewiesen ist.
quelle
override
in C ++ 11Der ganze Grund, warum Sie zusätzlich zu abstrakten Basisklassen in C # / Java eine spezielle Schnittstellentypkategorie haben, ist, dass C # / Java keine Mehrfachvererbung unterstützt.
C ++ unterstützt Mehrfachvererbung, daher wird kein spezieller Typ benötigt. Eine abstrakte Basisklasse ohne nicht abstrakte (rein virtuelle) Methoden entspricht funktional einer C # / Java-Schnittstelle.
quelle
Thread
Instanz ist. Mehrfachvererbung kann sowohl schlechtes Design als auch schlechte Zusammensetzung sein. Es hängt alles vom Fall ab.In C ++ gibt es kein Konzept für "Schnittstelle" an sich. AFAIK, Schnittstellen wurden zuerst in Java eingeführt, um das Fehlen einer Mehrfachvererbung zu umgehen. Dieses Konzept hat sich als sehr nützlich erwiesen, und der gleiche Effekt kann in C ++ durch Verwendung einer abstrakten Basisklasse erzielt werden.
Eine abstrakte Basisklasse ist eine Klasse, in der mindestens eine Mitgliedsfunktion (Methode in Java-Jargon) eine reine virtuelle Funktion ist, die mit der folgenden Syntax deklariert wird:
Eine abstrakte Basisklasse kann nicht instanziiert werden, dh Sie können kein Objekt der Klasse A deklarieren. Sie können nur Klassen von A ableiten, aber jede abgeleitete Klasse, die keine Implementierung von bereitstellt,
foo()
ist auch abstrakt. Um nicht mehr abstrakt zu sein, muss eine abgeleitete Klasse Implementierungen für alle reinen virtuellen Funktionen bereitstellen, die sie erbt.Beachten Sie, dass eine abstrakte Basisklasse mehr als eine Schnittstelle sein kann, da sie Datenelemente und Elementfunktionen enthalten kann, die nicht rein virtuell sind. Ein Äquivalent einer Schnittstelle wäre eine abstrakte Basisklasse ohne Daten mit nur rein virtuellen Funktionen.
Und wie Mark Ransom betonte, sollte eine abstrakte Basisklasse einen virtuellen Destruktor bereitstellen, genau wie jede Basisklasse.
quelle
Soweit ich testen konnte, ist es sehr wichtig, den virtuellen Destruktor hinzuzufügen. Ich verwende Objekte, die mit erstellt
new
und mit zerstört wurdendelete
.Wenn Sie den virtuellen Destruktor nicht zur Schnittstelle hinzufügen, wird der Destruktor der geerbten Klasse nicht aufgerufen.
Wenn Sie den vorherigen Code ohne ausführen
virtual ~IBase() {};
, werden Sie feststellen, dass der DestruktorTester::~Tester()
niemals aufgerufen wird.quelle
Meine Antwort ist im Grunde die gleiche wie die der anderen, aber ich denke, es gibt zwei weitere wichtige Dinge zu tun:
Deklarieren Sie einen virtuellen Destruktor in Ihrer Benutzeroberfläche oder erstellen Sie einen geschützten nicht virtuellen Destruktor, um undefiniertes Verhalten zu vermeiden, wenn jemand versucht, ein Objekt vom Typ zu löschen
IDemo
.Verwenden Sie die virtuelle Vererbung, um Probleme mit der Mehrfachvererbung zu vermeiden. (Es gibt häufiger Mehrfachvererbung, wenn wir Schnittstellen verwenden.)
Und wie andere Antworten:
Verwenden Sie die Schnittstelle, indem Sie eine andere Klasse erstellen, die diese virtuellen Methoden überschreibt.
Oder
Und
quelle
In C ++ 11 können Sie die Vererbung ganz einfach vermeiden:
In diesem Fall verfügt eine Schnittstelle über eine Referenzsemantik, dh Sie müssen sicherstellen, dass das Objekt die Schnittstelle überlebt (es ist auch möglich, Schnittstellen mit Wertesemantik zu erstellen).
Diese Art von Schnittstellen hat ihre Vor- und Nachteile:
Schließlich ist die Vererbung die Wurzel allen Übels im komplexen Software-Design. In Sean Parent's Value Semantics and Concepts-based Polymorphism (sehr empfohlene, bessere Versionen dieser Technik werden dort erklärt) wird der folgende Fall untersucht:
Angenommen, ich habe eine Anwendung, in der ich über die
MyShape
Benutzeroberfläche polymorph mit meinen Formen umgehe :In Ihrer Anwendung tun Sie dasselbe mit verschiedenen Formen über die
YourShape
Benutzeroberfläche:Angenommen, Sie möchten einige der Formen verwenden, die ich in Ihrer Anwendung entwickelt habe. Konzeptionell haben unsere Formen dieselbe Oberfläche, aber damit meine Formen in Ihrer Anwendung funktionieren, müssen Sie meine Formen wie folgt erweitern:
Erstens ist es möglicherweise überhaupt nicht möglich, meine Formen zu ändern. Darüber hinaus führt die Mehrfachvererbung zum Spaghetti-Code (stellen Sie sich vor, ein drittes Projekt verwendet die
TheirShape
Schnittstelle ... was passiert, wenn sie auch ihre Zeichenfunktion aufrufenmy_draw
?).Update: Es gibt einige neue Referenzen zum nicht vererbungsbasierten Polymorphismus:
quelle
Circle
Klasse ist ein schlechtes Design.Adapter
In solchen Fällen sollten Sie Muster verwenden. Es tut mir leid, wenn es ein bisschen hart klingt, aber versuchen Sie, eine Bibliothek aus dem wirklichen Leben zu verwenden,Qt
bevor Sie über die Vererbung urteilen. Vererbung macht das Leben viel einfacher.Adapter
Musters reparieren können ? Ich bin interessiert, seine Vorteile zu sehen.Square
es nicht schon da ist? Vorwissen? Deshalb ist es von der Realität losgelöst. Wenn Sie sich in Wirklichkeit auf die "MyShape" -Bibliothek verlassen, können Sie diese von Anfang an in die Benutzeroberfläche übernehmen. Im Beispiel für Formen gibt es viele Unsinns (einer davon ist, dass Sie zweiCircle
Strukturen haben), aber der Adapter würde ungefähr so aussehen -> ideone.com/UogjWkAlle guten Antworten oben. Eine zusätzliche Sache, die Sie beachten sollten - Sie können auch einen reinen virtuellen Destruktor haben. Der einzige Unterschied besteht darin, dass Sie es noch implementieren müssen.
Verwirrt?
Der Hauptgrund, warum Sie dies tun möchten, ist, wenn Sie wie ich Schnittstellenmethoden bereitstellen möchten, diese jedoch optional überschreiben möchten.
Um die Klasse zu einer Schnittstellenklasse zu machen, ist eine rein virtuelle Methode erforderlich. Alle Ihre virtuellen Methoden verfügen jedoch über Standardimplementierungen. Die einzige Methode, die zur reinen virtuellen Methode übrig bleibt, ist der Destruktor.
Die Neuimplementierung eines Destruktors in der abgeleiteten Klasse ist überhaupt keine große Sache - ich implementiere immer einen virtuellen oder nicht virtuellen Destruktor in meinen abgeleiteten Klassen neu.
quelle
Wenn Sie den C ++ - Compiler von Microsoft verwenden, können Sie Folgendes tun:
Ich mag diesen Ansatz, weil er zu einem viel kleineren Schnittstellencode führt und die generierte Codegröße erheblich kleiner sein kann. Durch die Verwendung von novtable werden alle Verweise auf den vtable-Zeiger in dieser Klasse entfernt, sodass Sie ihn niemals direkt instanziieren können. Siehe die Dokumentation hier - novtable .
quelle
novtable
über Standard verwendet habenvirtual void Bar() = 0;
= 0;
das ich hinzugefügt habe). Lesen Sie die Dokumentation, wenn Sie sie nicht verstehen.= 0;
und nahm an, dass es nur eine nicht standardmäßige Methode war, genau dasselbe zu tun.Eine kleine Ergänzung zu dem, was dort geschrieben steht:
Stellen Sie zunächst sicher, dass Ihr Destruktor auch rein virtuell ist
Zweitens möchten Sie möglicherweise virtuell (und nicht normal) erben, wenn Sie implementieren, nur für gute Maßnahmen.
quelle
Sie können auch Vertragsklassen berücksichtigen, die mit dem NVI (Non Virtual Interface Pattern) implementiert wurden. Zum Beispiel:
quelle
Ich bin noch neu in der C ++ - Entwicklung. Ich habe mit Visual Studio (VS) begonnen.
Dennoch scheint niemand das
__interface
in VS (.NET) zu erwähnen . Ich bin mir nicht sicher, ob dies eine gute Möglichkeit ist, eine Schnittstelle zu deklarieren. Es scheint jedoch eine zusätzliche Durchsetzung zu bieten (in den Dokumenten erwähnt ). So dass Sie das nicht explizit angeben müssenvirtual TYPE Method() = 0;
, da es automatisch konvertiert wird.Wenn jemand etwas Interessantes daran hat, teilen Sie es bitte mit. :-)
Vielen Dank.
quelle
Es ist zwar wahr, dass dies
virtual
der De-facto-Standard zum Definieren einer Schnittstelle ist, aber vergessen wir nicht das klassische C-ähnliche Muster, das mit einem Konstruktor in C ++ geliefert wird:Dies hat den Vorteil, dass Sie die Laufzeit von Ereignissen neu binden können, ohne Ihre Klasse erneut erstellen zu müssen (da C ++ keine Syntax zum Ändern polymorpher Typen hat, ist dies eine Problemumgehung für Chamäleonklassen).
Tipps:
click
den Konstruktor Ihres Nachkommens ausfüllen .protected
Mitglied und einepublic
Referenz und / oder einen Getter.if
Änderungen von s gegenüber dem Status in Ihrem Code ist dies möglicherweise schneller alsswitch()
es oderif
s (der Turnaround wird um 3-4if
s erwartet , aber messen Sie immer zuerst.std::function<>
Funktionszeiger auswählen , können Sie möglicherweise alle Ihre Objektdaten darin verwaltenIBase
. Ab diesem Punkt können Sie Wertschemata für habenIBase
(z. B.std::vector<IBase>
wird funktionieren). Beachten Sie, dass dies je nach Compiler und STL-Code möglicherweise langsamer ist. Außerdem haben aktuelle Implementierungen von imstd::function<>
Vergleich zu Funktionszeigern oder sogar virtuellen Funktionen tendenziell einen Overhead (dies könnte sich in Zukunft ändern).quelle
Hier ist die Definition von
abstract class
in c ++ Standardn4687
13.4.2
quelle
Ergebnis: Rechteckfläche: 35 Dreiecksfläche: 17
Wir haben gesehen, wie eine abstrakte Klasse eine Schnittstelle in Bezug auf getArea () definierte und zwei andere Klassen dieselbe Funktion implementierten, jedoch mit unterschiedlichen Algorithmen, um die formspezifische Fläche zu berechnen.
quelle