Wie kann ich eine Klasse einrichten, die eine Schnittstelle darstellt? Ist dies nur eine abstrakte Basisklasse?
Ist eine virtuelle Vererbung nicht erforderlich, da Sie keine Datenelemente in einer Schnittstelle haben.
Wie kann ich eine Klasse einrichten, die eine Schnittstelle darstellt? Ist dies nur eine abstrakte Basisklasse?
Meine Antwort ist im Grunde dieselbe wie die der anderen, aber ich denke, es gibt noch zwei weitere wichtige Dinge zu tun:
Deklarieren Sie einen virtuellen Destruktor in Ihrer Schnittstelle oder erstellen Sie einen geschützten nicht-virtuellen Destruktor, um undefiniertes Verhalten zu vermeiden, wenn jemand versucht, ein Objekt des Typs IDemo
.
Verwenden Sie die virtuelle Vererbung, um Probleme bei der Mehrfachvererbung zu vermeiden. (Bei der Verwendung von Schnittstellen kommt es häufiger zu Mehrfachvererbung).
Und wie andere Antworten:
Verwenden Sie die Schnittstelle, indem Sie eine andere Klasse erstellen, die diese virtuellen Methoden außer Kraft setzt.
class IDemo
{
public:
virtual void OverrideMe() = 0;
virtual ~IDemo() {}
}
O
class IDemo
{
public:
virtual void OverrideMe() = 0;
protected:
~IDemo() {}
}
Und
class Child : virtual public IDemo
{
public:
virtual void OverrideMe()
{
//do stuff
}
}
Ist eine virtuelle Vererbung nicht erforderlich, da Sie keine Datenelemente in einer Schnittstelle haben.
Die virtuelle Vererbung ist auch für Methoden wichtig. Ohne sie werden Sie mit OverrideMe() auf Unklarheiten stoßen, selbst wenn eine der "Instanzen" davon rein virtuell ist (ich habe das gerade selbst ausprobiert).
@Avishay_ " ist eine virtuelle Vererbung nicht erforderlich, da Sie keine Datenelemente in einer Schnittstelle haben. " Falsch.
In C++11 können Sie die Vererbung leicht ganz vermeiden:
struct Interface {
explicit Interface(SomeType& other)
: foo([=](){ return other.my_foo(); }),
bar([=](){ return other.my_bar(); }), /*...*/ {}
explicit Interface(SomeOtherType& other)
: foo([=](){ return other.some_foo(); }),
bar([=](){ return other.some_bar(); }), /*...*/ {}
// you can add more types here...
// or use a generic constructor:
template<class T>
explicit Interface(T& other)
: foo([=](){ return other.foo(); }),
bar([=](){ return other.bar(); }), /*...*/ {}
const std::function<void(std::string)> foo;
const std::function<void(std::string)> bar;
// ...
};
In diesem Fall hat eine Schnittstelle Referenzsemantik, d.h. man muss sicherstellen, dass das Objekt die Schnittstelle überlebt (es ist auch möglich, Schnittstellen mit Wertsemantik zu erstellen).
Diese Arten von Schnittstellen haben ihre Vor- und Nachteile:
Schließlich ist die Vererbung die Wurzel allen Übels bei der Entwicklung komplexer Software. Unter Sean Parents Wertesemantik und konzeptbasierte Polymorphie (sehr empfehlenswert, dort werden bessere Versionen dieser Technik erläutert) wird der folgende Fall untersucht:
Angenommen, ich habe eine Anwendung, in der ich meine Formen polymorph mit der MyShape
Schnittstelle:
struct MyShape { virtual void my_draw() = 0; };
struct Circle : MyShape { void my_draw() { /* ... */ } };
// more shapes: e.g. triangle
In Ihrer Anwendung tun Sie dasselbe mit verschiedenen Formen, indem Sie die YourShape
Schnittstelle:
struct YourShape { virtual void your_draw() = 0; };
struct Square : YourShape { void your_draw() { /* ... */ } };
/// some more shapes here...
Nehmen wir nun an, Sie möchten einige der von mir entwickelten Formen in Ihrer Anwendung verwenden. Vom Konzept her haben unsere Formen die gleiche Schnittstelle, aber damit meine Formen in Ihrer Anwendung funktionieren, müssten Sie meine Formen wie folgt erweitern:
struct Circle : MyShape, YourShape {
void my_draw() { /*stays the same*/ };
void your_draw() { my_draw(); }
};
Erstens ist es vielleicht gar nicht möglich, meine Formen zu ändern. Außerdem führt die Mehrfachvererbung zu Spaghetti-Code (stellen Sie sich vor, es kommt ein drittes Projekt hinzu, das die TheirShape
Schnittstelle... was passiert, wenn sie auch ihre Zeichenfunktion aufrufen my_draw
?).
Update: Es gibt ein paar neue Referenzen über Polymorphismus, der nicht auf Vererbung basiert:
Ehrlich gesagt ist Vererbung viel klarer als dieses C++11-Ding, das vorgibt, eine Schnittstelle zu sein, aber eher ein Klebstoff ist, um einige inkonsistente Designs zu verbinden. Das Beispiel mit den Formen ist von der Realität losgelöst und Circle
Klasse ist ein schlechtes Design. Sie sollten verwenden Adapter
Muster in solchen Fällen. Es tut mir leid, wenn es ein wenig hart klingt, aber versuchen Sie, eine Bibliothek aus dem wirklichen Leben zu verwenden, wie Qt
bevor sie über die Vererbung urteilen. Erben macht das Leben viel einfacher.
Das klingt überhaupt nicht hart. Inwiefern ist das Beispiel der Form von der Realität abgekoppelt? Könnten Sie ein Beispiel (vielleicht auf ideone) für die Fixierung eines Kreises mit Hilfe des Adapter
Muster? Ich bin daran interessiert, seine Vorteile zu sehen.
Okay, ich werde versuchen, in diesen kleinen Kasten zu passen. Zunächst einmal wählt man normalerweise Bibliotheken wie "MyShape" aus, bevor man mit dem Schreiben einer eigenen Anwendung beginnt, um seine Arbeit zu sichern. Wie könnten Sie sonst wissen Square
nicht bereits vorhanden ist? Vorauswissen? Das ist der Grund, warum es von der Realität losgelöst ist. Und in der Realität, wenn Sie sich für die Bibliothek "MyShape" entscheiden, können Sie sich von Anfang an an deren Schnittstelle anpassen. In Shapes Beispiel gibt es viele Unsinnigkeiten (eine davon ist, dass Sie zwei Circle
structs), aber der Adapter würde etwa so aussehen -> ideone.com/UogjWk
Alle oben genannten Antworten sind gut. Eine zusätzliche Sache, die Sie im Auge behalten sollten - Sie können auch einen rein virtuellen Destruktor haben. Der einzige Unterschied ist, dass Sie ihn noch implementieren müssen.
Verwirrt?
--- header file ----
class foo {
public:
foo() {;}
virtual ~foo() = 0;
virtual bool overrideMe() {return false;}
};
---- source ----
foo::~foo()
{
}
Der Hauptgrund, warum Sie dies tun sollten, ist, wenn Sie, wie ich, Schnittstellenmethoden bereitstellen möchten, deren Überschreibung aber optional ist.
Um die Klasse zu einer Schnittstellenklasse zu machen, ist eine rein virtuelle Methode erforderlich, aber alle Ihre virtuellen Methoden haben Standardimplementierungen, so dass die einzige Methode, die noch rein virtuell gemacht werden muss, der Destruktor ist.
Die Reimplementierung eines Destruktors in der abgeleiteten Klasse ist überhaupt keine große Sache - ich reimplementiere immer einen Destruktor, ob virtuell oder nicht, in meinen abgeleiteten Klassen.
Warum, oh warum, sollte jemand den dtor in diesem Fall rein virtuell machen wollen? Was wäre der Vorteil davon? Man würde den abgeleiteten Klassen nur etwas aufzwingen, das sie wahrscheinlich nicht brauchen - einen dtor.
Sie können auch Vertragsklassen in Betracht ziehen, die mit dem NVI (Non Virtual Interface Pattern) implementiert werden. Zum Beispiel:
struct Contract1 : boost::noncopyable
{
virtual ~Contract1() = default;
void f(Parameters p) {
assert(checkFPreconditions(p)&&"Contract1::f, pre-condition failure");
// + class invariants.
do_f(p);
// Check post-conditions + class invariants.
}
private:
virtual void do_f(Parameters p) = 0;
};
...
class Concrete : public Contract1, public Contract2
{
private:
void do_f(Parameters p) override; // From contract 1.
void do_g(Parameters p) override; // From contract 2.
};
Für andere Leser ist dies Dr. Dobbs Artikel "Gespräche: Virtually Yours" von Jim Hyslop und Herb Sutter geht etwas näher darauf ein, warum man den NVI verwenden sollte.
Wenn Sie den C++-Compiler von Microsoft verwenden, können Sie wie folgt vorgehen:
struct __declspec(novtable) IFoo
{
virtual void Bar() = 0;
};
class Child : public IFoo
{
public:
virtual void Bar() override { /* Do Something */ }
}
Ich mag diesen Ansatz, weil er zu einem viel kleineren Schnittstellencode führt und die Größe des generierten Codes deutlich geringer sein kann. Die Verwendung von novtable entfernt alle Verweise auf den vtable-Zeiger in dieser Klasse, so dass Sie ihn niemals direkt instanziieren können. Siehe die Dokumentation hier - novable .
Ich verstehe nicht ganz, warum Sie den Begriff novtable
über Standard virtual void Bar() = 0;
Es ist zusätzlich zu (ich habe gerade die fehlende = 0;
die ich hinzugefügt habe). Lesen Sie die Dokumentation, wenn Sie es nicht verstehen.
CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.