893 Stimmen

Wie deklariert man eine Schnittstelle in C++?

Wie kann ich eine Klasse einrichten, die eine Schnittstelle darstellt? Ist dies nur eine abstrakte Basisklasse?

746voto

Mark Ransom Punkte 283960

Zur Erweiterung der Antwort von bradtgmurray können Sie eine Ausnahme von der rein virtuellen Methodenliste Ihrer Schnittstelle machen, indem Sie einen virtuellen Destruktor hinzufügen. Damit können Sie den Besitz eines Zeigers an eine andere Partei weitergeben, ohne die konkrete abgeleitete Klasse offenzulegen. Der Destruktor muss nichts tun, da die Schnittstelle keine konkreten Mitglieder hat. Es mag widersprüchlich erscheinen, eine Funktion sowohl als virtuell als auch als Inline zu definieren, aber glauben Sie mir - das ist es nicht.

class IDemo
{
    public:
        virtual ~IDemo() {}
        virtual void OverrideMe() = 0;
};

class Parent
{
    public:
        virtual ~Parent();
};

class Child : public Parent, public IDemo
{
    public:
        virtual void OverrideMe()
        {
            //do stuff
        }
};

Sie müssen keinen Body für den virtuellen Destruktor einfügen - es hat sich herausgestellt, dass einige Compiler Probleme haben, einen leeren Destruktor zu optimieren, und Sie sind besser dran, wenn Sie den Standard verwenden.

121 Stimmen

Virtueller Desktuktor++! Dies ist sehr wichtig. Sie können auch rein virtuelle Deklarationen der Operator=- und Kopierkonstruktor-Definitionen einfügen, um zu verhindern, dass der Compiler diese automatisch für Sie generiert.

39 Stimmen

Eine Alternative zu einem virtuellen Destruktor ist ein geschützter Destruktor. Dadurch wird die polymorphe Zerstörung deaktiviert, was unter bestimmten Umständen sinnvoller sein kann. Siehe "Leitlinie #4" in gotw.ca/veroeffentlichungen/mill18.htm .

4 Stimmen

Wenn Sie wissen, dass Sie die Klasse nicht über eine Basisklasse löschen werden, brauchen Sie den virtuellen Destruktor nicht. Allerdings schadet es nie wirklich, den Destruktor virtuell zu machen (außer für einen vtable lookup, oh nein!).

269voto

bradtgmurray Punkte 12945

Erstellen Sie eine Klasse mit rein virtuellen Methoden. Verwenden Sie die Schnittstelle, indem Sie eine andere Klasse erstellen, die diese virtuellen Methoden außer Kraft setzt.

Eine rein virtuelle Methode ist eine Klassenmethode, die als virtuell definiert und 0 zugewiesen ist.

class IDemo
{
    public:
        virtual ~IDemo() {}
        virtual void OverrideMe() = 0;
};

class Child : public IDemo
{
    public:
        virtual void OverrideMe()
        {
            // do stuff
        }
};

34 Stimmen

Sie sollten eine do nothing destructor in IDemo haben, so dass es definiert ist, Verhalten zu tun: IDemo *p = new Child; /*whatever */ delete p;

13 Stimmen

Warum ist die Methode OverrideMe in der Klasse Child virtuell? Ist das notwendig?

11 Stimmen

@Cemre - nein, es ist nicht notwendig, aber es schadet auch nicht.

169voto

Joel Coehoorn Punkte 377088

Der ganze Grund, warum es in C#/ zusätzlich zu den abstrakten Basisklassen eine spezielle Interface-Typ-Kategorie gibt Java ist, weil C#/Java keine Mehrfachvererbung unterstützen.

C++ unterstützt Mehrfachvererbung, so dass ein spezieller Typ nicht erforderlich ist. Eine abstrakte Basisklasse ohne nicht-abstrakte (rein virtuelle) Methoden ist funktional äquivalent zu einer C#/Java-Schnittstelle.

22 Stimmen

Es wäre trotzdem schön, Schnittstellen erstellen zu können, damit wir nicht so viel tippen müssen (virtual , =0, virtual destructor). Auch Mehrfachvererbung scheint mir eine wirklich schlechte Idee zu sein, und ich habe noch nie gesehen, dass sie in der Praxis eingesetzt wird, aber Schnittstellen werden ständig gebraucht. Schade, dass die C++-Gemeinschaft keine Schnittstellen einführen wird, nur weil ich sie haben will.

10 Stimmen

Ha11erlaubt: Es hat Schnittstellen. Das sind Klassen mit rein virtuellen Methoden und ohne Methodenimplementierungen.

0 Stimmen

@Ha11owed Es gibt einige Macken in Java, die durch das Fehlen von Mehrfachvererbung verursacht werden. Schauen Sie sich an java.lang.Thread Klasse und java.lang.Runnable Schnittstelle, die nur deshalb existiert, weil man sie nicht erweitern kann Thread zusammen mit einer anderen Basis. Aus der Dokumentation mag es so aussehen, als ob es zur freien Verfügung stünde, aber ich war einmal gezwungen, die Runnable wegen des Fehlens einer Mehrfachvererbung.

62voto

Dima Punkte 37984

In C++ gibt es kein Konzept der "Schnittstelle" per se. AFAIK wurden Schnittstellen zuerst in Java eingeführt, um das Fehlen der Mehrfachvererbung zu umgehen. Dieses Konzept hat sich als sehr nützlich erwiesen, und der gleiche Effekt kann in C++ durch die Verwendung einer abstrakten Basisklasse erzielt werden.

Eine abstrakte Basisklasse ist eine Klasse, in der mindestens eine Mitgliedsfunktion (Methode im Java-Jargon) eine rein virtuelle Funktion ist, die mit der folgenden Syntax deklariert wird:

class A
{
  virtual void foo() = 0;
};

Eine abstrakte Basisklasse kann nicht instanziiert werden, d. h. 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 foo() wird auch abstrakt sein. 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 sein kann als eine Schnittstelle, da sie Datenmitglieder und Mitgliedsfunktionen enthalten kann, die nicht rein virtuell sind. Ein Äquivalent zu 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, wie jede andere Basisklasse übrigens auch.

14 Stimmen

Mehr als "fehlende Mehrfachvererbung" würde ich sagen, um die Mehrfachvererbung zu ersetzen. Java wurde von Anfang an so konzipiert, weil Mehrfachvererbung mehr Probleme schafft als sie löst. Gute Antwort

12 Stimmen

Oscar, das hängt davon ab, ob Sie ein C++-Programmierer sind, der Java gelernt hat, oder umgekehrt :) IMHO löst die Mehrfachvererbung, wenn sie mit Bedacht eingesetzt wird, wie fast alles in C++ Probleme. Eine abstrakte Basisklasse "Schnittstelle" ist ein Beispiel für eine sehr vernünftige Verwendung von Mehrfachvererbung.

10 Stimmen

@OscarRyz Falsch. MI schafft nur dann Probleme, wenn es missbraucht wird. Die meisten angeblichen Probleme mit MI würden sich auch bei alternativen Designs (ohne MI) ergeben. Wenn Menschen ein Problem mit ihrem Design mit MI haben, ist es die Schuld von MI; wenn sie ein Designproblem mit SI haben, ist es ihre eigene Schuld. Der "Diamant des Todes" (wiederholte Vererbung) ist ein Paradebeispiel. MI-Bashing ist keine reine Heuchelei, aber nahe dran.

48voto

Carlos C Soto Punkte 955

Soweit ich testen konnte, ist es sehr wichtig, den virtuellen Destruktor hinzuzufügen. Ich verwende Objekte, die mit new und zerstört mit delete .

Wenn Sie den virtuellen Destruktor nicht in die Schnittstelle aufnehmen, wird der Destruktor der geerbten Klasse nicht aufgerufen.

class IBase {
public:
    virtual ~IBase() {}; // destructor, use it to call destructor of the inherit classes
    virtual void Describe() = 0; // pure virtual method
};

class Tester : public IBase {
public:
    Tester(std::string name);
    virtual ~Tester();
    virtual void Describe();
private:
    std::string privatename;
};

Tester::Tester(std::string name) {
    std::cout << "Tester constructor" << std::endl;
    this->privatename = name;
}

Tester::~Tester() {
    std::cout << "Tester destructor" << std::endl;
}

void Tester::Describe() {
    std::cout << "I'm Tester [" << this->privatename << "]" << std::endl;
}

void descriptor(IBase * obj) {
    obj->Describe();
}

int main(int argc, char** argv) {

    std::cout << std::endl << "Tester Testing..." << std::endl;
    Tester * obj1 = new Tester("Declared with Tester");
    descriptor(obj1);
    delete obj1;

    std::cout << std::endl << "IBase Testing..." << std::endl;
    IBase * obj2 = new Tester("Declared with IBase");
    descriptor(obj2);
    delete obj2;

    // this is a bad usage of the object since it is created with "new" but there are no "delete"
    std::cout << std::endl << "Tester not defined..." << std::endl;
    descriptor(new Tester("Not defined"));

    return 0;
}

Wenn Sie den vorherigen Code ohne virtual ~IBase() {}; werden Sie sehen, dass der Destruktor Tester::~Tester() wird nie aufgerufen.

4 Stimmen

Die beste Antwort auf dieser Seite, da sie ein praktisches, kompilierbares Beispiel liefert. Prost!

1 Stimmen

Testet::~Tester() wird nur ausgeführt, wenn das Objekt "Mit Tester deklariert" ist.

0 Stimmen

Tatsächlich wird der Destruktor der Zeichenkette privateame aufgerufen, und im Speicher ist das alles, wofür er zugewiesen wird. Was die Laufzeit betrifft, so wird mit der Zerstörung aller konkreten Mitglieder einer Klasse auch die Klasseninstanz zerstört. Ich habe ein ähnliches Experiment mit einer Line-Klasse durchgeführt, die zwei Point-Strukturen hatte, und festgestellt, dass beide Strukturen bei einem Löschaufruf oder der Rückkehr aus der umgebenden Funktion zerstört wurden (Ha!). valgrind bestätigte 0 Lecks.

CodeJaeger.com

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.

Powered by:

X