Wie deklariert man eine Schnittstelle in C ++?


Antworten:


686

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.

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 stellt sich heraus, dass einige Compiler Probleme haben, einen leeren Destruktor zu optimieren, und Sie sollten die Standardeinstellung verwenden.


106
Virtueller Desctuctor ++! Dies ist sehr wichtig. Möglicherweise möchten Sie auch reine virtuelle Deklarationen des Operators = einfügen und Konstruktordefinitionen kopieren, um zu verhindern, dass der Compiler diese automatisch für Sie generiert.
Xan

33
Eine Alternative zu einem virtuellen Destruktor ist ein geschützter Destruktor. Dies deaktiviert die polymorphe Zerstörung, die unter bestimmten Umständen angemessener sein kann. Suchen Sie unter gotw.ca/publications/mill18.htm nach "Guideline # 4" .
Fred Larson

9
Eine andere Möglichkeit besteht darin, einen reinen virtuellen ( =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 einen thisZeiger (wenn das konstruierte Objekt noch vom ParentTyp ist), und daher muss der Compiler eine gültige vtable bereitstellen. Wenn Sie also thiswährend der Erstellung nicht explizit virtuelle Destruktoren über aufrufen :), können Sie Codegröße sparen.
Pavel Minaev

51
Wie typisch für eine C ++ - Antwort ist, dass die Top-Antwort die Frage nicht direkt beantwortet (obwohl der Code offensichtlich perfekt ist), sondern die einfache Antwort optimiert.
Tim

18
Vergessen Sie nicht, dass Sie in C ++ 11 das overrideSchlü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;
Sean

245

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.

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

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

29
Sie sollten einen ID-Destruktor in IDemo haben, damit definiertes Verhalten definiert wird: IDemo * p = new Child; / * was auch immer * / lösche p;
Evan Teran

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

9
@Cemre - nein, es ist nicht notwendig, aber es tut auch nicht weh.
PowerApp101

11
Im Allgemeinen ist es eine gute Idee, das Schlüsselwort "virtuell" beizubehalten, wenn eine virtuelle Methode überschrieben wird. Obwohl dies nicht erforderlich ist, kann es den Code klarer machen. Andernfalls haben Sie keinen Hinweis darauf, dass diese Methode polymorph verwendet werden könnte oder sogar in der Basisklasse vorhanden ist.
Kevin

27
@ Kevin Außer mit overridein C ++ 11
Keyser

146

Der 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.


17
Es wäre immer noch schön, Schnittstellen erstellen zu können, um nicht so viel zu tippen (virtuell, = 0, virtueller Destruktor). Auch Mehrfachvererbung scheint mir eine wirklich schlechte Idee zu sein, und ich habe sie in der Praxis noch nie gesehen, aber Schnittstellen werden ständig benötigt. Schade, dass die C ++ - Comity keine Schnittstellen einführt, nur weil ich sie will.
Ha11owed

9
Ha11owed: Es hat Schnittstellen. Sie werden Klassen mit rein virtuellen Methoden und ohne Methodenimplementierungen genannt.
Miles Rout

6
@doc: java.lang.Thread enthält Methoden und Konstanten, die Sie wahrscheinlich nicht in Ihrem Objekt haben möchten. Was soll der Compiler tun, wenn Sie von Thread und einer anderen Klasse mit der öffentlichen Methode checkAccess () erweitern? Möchten Sie wirklich lieber stark benannte Basiszeiger wie in C ++ verwenden? Dies scheint ein schlechtes Design zu sein. Normalerweise benötigen Sie eine Komposition, bei der Sie der Meinung sind, dass Sie mehrere Vererbungen benötigen.
Ha11owed

4
@ Ha11owed es ist lange her, also erinnere ich mich nicht an Details, aber es hatte Methoden und Contants, die ich in meiner Klasse haben wollte und was noch wichtiger ist, ich wollte, dass mein abgeleitetes Klassenobjekt eine ThreadInstanz ist. Mehrfachvererbung kann sowohl schlechtes Design als auch schlechte Zusammensetzung sein. Es hängt alles vom Fall ab.
Doc

2
@ Dave: Wirklich? Objective-C hat eine Bewertung zur Kompilierungszeit und Vorlagen?
Deduplikator

51

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:

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

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.


13
Mehr als "Mangel an Mehrfachvererbung" würde ich sagen, um Mehrfachvererbung zu ersetzen. Java wurde von Anfang an so konzipiert, weil Mehrfachvererbung mehr Probleme verursacht als das, was es löst. Gute Antwort
OscarRyz

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

8
@OscarRyz Falsch. MI schafft nur dann Probleme, wenn es missbraucht wird. Die meisten angeblichen Probleme mit MI würden auch mit alternativen Designs (ohne MI) einhergehen. Wenn Leute 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. "Diamond of Death" (wiederholte Vererbung) ist ein Paradebeispiel. MI-Bashing ist keine reine Heuchelei, sondern nahe.
Neugieriger

4
Semantisch unterscheiden sich Schnittstellen von abstrakten Klassen, sodass die Java-Schnittstellen nicht nur eine technische Problemumgehung darstellen. Die Wahl zwischen der Definition einer Schnittstelle oder einer abstrakten Klasse hängt von der Semantik ab, nicht von technischen Überlegungen. Stellen wir uns eine Schnittstelle "HasEngine" vor: Dies ist ein Aspekt, eine Funktion, die von sehr unterschiedlichen Typen (ob Klassen oder abstrakte Klassen) angewendet / implementiert werden kann. Daher definieren wir eine Schnittstelle dafür, keine abstrakte Klasse.
Marek Stanley

2
@MarekStanley, Sie haben vielleicht Recht, aber ich wünschte, Sie hätten ein besseres Beispiel ausgewählt. Ich denke gerne daran, eine Schnittstelle zu erben oder eine Implementierung zu erben. In C ++ können Sie entweder die Schnittstelle und die Implementierung zusammen erben (öffentliche Vererbung) oder Sie können nur die Implementierung erben (private Vererbung). In Java haben Sie die Möglichkeit, nur die Schnittstelle ohne Implementierung zu erben.
Dima

43

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

Wenn Sie den virtuellen Destruktor nicht zur Schnittstelle hinzufügen, 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 ausführen virtual ~IBase() {};, werden Sie feststellen, dass der Destruktor Tester::~Tester()niemals aufgerufen wird.


3
Die beste Antwort auf dieser Seite, da es darauf ankommt, ein praktisches, kompilierbares Beispiel zu liefern. Prost!
Lumi

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

Tatsächlich wird der Destruktor des String-Privatnamens aufgerufen, und im Speicher ist dies alles, was dort zugewiesen wird. Wenn zur Laufzeit alle konkreten Mitglieder einer Klasse zerstört werden, gilt dies auch für die Klasseninstanz. Ich habe ein ähnliches Experiment mit einer Linienklasse versucht, die zwei Punktstrukturen hatte, und festgestellt, dass beide Strukturen bei einem Löschaufruf oder bei der Rückkehr von der umfassenden Funktion zerstört wurden (Ha!). Valgrind bestätigte 0 Leck.
Chris Reid

33

Meine Antwort ist im Grunde die gleiche wie die der anderen, aber ich denke, es gibt zwei weitere wichtige Dinge zu tun:

  1. 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.

  2. 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:

  • Erstellen Sie eine Klasse mit rein virtuellen Methoden.
  • Verwenden Sie die Schnittstelle, indem Sie eine andere Klasse erstellen, die diese virtuellen Methoden überschreibt.

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

    Oder

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

    Und

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

2
Es ist keine virtuelle Vererbung erforderlich, da Sie keine Datenelemente in einer Schnittstelle haben.
Robocide

3
Die virtuelle Vererbung ist auch für Methoden wichtig. Ohne sie werden Sie mit OverrideMe () auf Mehrdeutigkeiten stoßen, selbst wenn eine der 'Instanzen' davon rein virtuell ist (haben Sie dies einfach selbst versucht).
Knarf Navillus

5
@Avishay_ " Es ist keine virtuelle Vererbung erforderlich, da Sie keine Datenelemente in einer Schnittstelle haben. " Falsch.
Neugieriger

Beachten Sie, dass die virtuelle Vererbung bei einigen gcc-Versionen möglicherweise nicht funktioniert, da Version 4.3.3 mit WinAVR 2010 ausgeliefert wird: gcc.gnu.org/bugzilla/show_bug.cgi?id=35067
mMontu

-1 für einen nicht virtuell geschützten Destruktor, sorry
Wolf

10

In C ++ 11 können Sie die Vererbung ganz einfach 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 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:

  • Sie benötigen mehr Speicher als vererbungsbasierter Polymorphismus.
  • Sie sind im Allgemeinen schneller als vererbungsbasierter Polymorphismus.
  • In den Fällen, in denen Sie den endgültigen Typ kennen, sind sie viel schneller! (Einige Compiler wie gcc und clang führen mehr Optimierungen bei Typen durch, die keine Typen mit virtuellen Funktionen haben / von diesen erben.)

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 MyShapeBenutzeroberfläche polymorph mit meinen Formen umgehe :

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 über die YourShapeBenutzeroberfläche:

struct YourShape { virtual void your_draw() = 0; };
struct Square : YourShape { void your_draw() { /* ... */ } };
/// some more shapes here...

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:

struct Circle : MyShape, YourShape { 
  void my_draw() { /*stays the same*/ };
  void your_draw() { my_draw(); }
};

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 TheirShapeSchnittstelle ... was passiert, wenn sie auch ihre Zeichenfunktion aufrufen my_draw?).

Update: Es gibt einige neue Referenzen zum nicht vererbungsbasierten Polymorphismus:


5
Die TBH-Vererbung ist weitaus klarer als die C ++ 11-Vererbung, die vorgibt, eine Schnittstelle zu sein, aber eher ein Klebstoff ist, um einige inkonsistente Designs zu binden. Das Beispiel der Formen ist von der Realität losgelöst und die CircleKlasse ist ein schlechtes Design. AdapterIn 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, Qtbevor Sie über die Vererbung urteilen. Vererbung macht das Leben viel einfacher.
Doc

2
Es klingt überhaupt nicht hart. Wie ist das Formbeispiel von der Realität losgelöst? Könnten Sie ein Beispiel (vielleicht auf ideone) geben, wie Sie Circle anhand des AdapterMusters reparieren können ? Ich bin interessiert, seine Vorteile zu sehen.
Gnzlbg

OK, ich werde versuchen, in diese winzige Schachtel zu passen. Zunächst wählen Sie normalerweise Bibliotheken wie "MyShape" aus, bevor Sie mit dem Schreiben Ihrer eigenen Anwendung beginnen, um Ihre Arbeit zu sichern. Wie kannst du sonst wissen, dass Squarees 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 zwei CircleStrukturen haben), aber der Adapter würde ungefähr so ​​aussehen -> ideone.com/UogjWk
doc

2
Es ist dann nicht von der Realität losgelöst. Wenn Unternehmen A Unternehmen B kauft und die Codebasis von Unternehmen B in die von A integrieren möchte, haben Sie zwei völlig unabhängige Codebasen. Stellen Sie sich vor, jeder hat eine Formhierarchie verschiedener Typen. Sie können sie nicht einfach mit Vererbung kombinieren und Firma C hinzufügen, und Sie haben ein großes Durcheinander. Ich denke, du solltest dir diesen Vortrag ansehen: youtube.com/watch?v=0I0FD3N5cgM Meine Antwort ist älter, aber du wirst die Ähnlichkeiten sehen. Sie müssen nicht immer alles neu implementieren, sondern können eine Implementierung in der Benutzeroberfläche bereitstellen und eine Mitgliedsfunktion auswählen, falls verfügbar.
Gnzlbg

1
Ich habe einen Teil des Videos gesehen und das ist völlig falsch. Ich verwende dynamic_cast nur zum Debuggen. Dynamische Besetzung bedeutet, dass etwas mit Ihrem Design nicht stimmt und die Designs in diesem Video von Natur aus falsch sind :). Guy erwähnt sogar Qt, aber auch hier liegt er falsch - QLayout erbt weder von QWidget noch umgekehrt!
doc

9

Alle 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?


    --- header file ----
    class foo {
    public:
      foo() {;}
      virtual ~foo() = 0;

      virtual bool overrideMe() {return false;}
    };

    ---- source ----
    foo::~foo()
    {
    }

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.


4
Warum, oh warum, sollte jemand den dtor in diesem Fall rein virtuell machen wollen? Was wäre der Gewinn davon? Sie würden den abgeleiteten Klassen einfach etwas aufzwingen, das sie wahrscheinlich nicht einschließen müssen - einen dtor.
Johann Gerell

6
Meine Antwort wurde aktualisiert, um Ihre Frage zu beantworten. Ein reiner virtueller Destruktor ist ein gültiger Weg, um eine Schnittstellenklasse zu erreichen (der einzige Weg, um dies zu erreichen?), In der alle Methoden Standardimplementierungen haben.
Rodyland

7

Wenn Sie den C ++ - Compiler von Microsoft verwenden, können Sie Folgendes tun:

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 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 .


4
Ich verstehe nicht ganz, warum Sie novtableüber Standard verwendet habenvirtual void Bar() = 0;
Flexo

2
Es ist zusätzlich zu (ich habe gerade das Fehlen bemerkt, = 0;das ich hinzugefügt habe). Lesen Sie die Dokumentation, wenn Sie sie nicht verstehen.
Mark Ingram

Ich las es ohne das = 0;und nahm an, dass es nur eine nicht standardmäßige Methode war, genau dasselbe zu tun.
Flexo

4

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.


Ich mag virtuelle Vererbung, weil es konzeptionell bedeutet, dass es nur eine Instanz der geerbten Klasse gibt. Zugegeben, die Klasse hier hat keinen Platzbedarf, so dass sie möglicherweise überflüssig ist. Ich habe MI in C ++ schon eine Weile nicht mehr gemacht, aber würde eine nicht-virtuelle Vererbung das Upcasting nicht erschweren?
Uri

Warum, oh warum, sollte jemand den dtor in diesem Fall rein virtuell machen wollen? Was wäre der Gewinn davon? Sie würden den abgeleiteten Klassen einfach etwas aufzwingen, das sie wahrscheinlich nicht einschließen müssen - einen dtor.
Johann Gerell

2
Wenn es eine Situation gibt, in der ein Objekt durch einen Zeiger auf die Schnittstelle zerstört wird, sollten Sie sicherstellen, dass der Destruktor virtuell ist ...
Uri

An einem reinen virtuellen Destruktor ist nichts auszusetzen. Es ist nicht notwendig, aber es ist nichts falsch daran. Das Implementieren eines Destruktors in einer abgeleiteten Klasse ist für den Implementierer dieser Klasse kaum eine große Belastung. In meiner Antwort unten erfahren Sie, warum Sie dies tun würden.
Rodyland

+1 für die virtuelle Vererbung, da es bei Schnittstellen wahrscheinlicher ist, dass die Klasse die Schnittstelle von zwei oder mehr Pfaden ableitet. Ich entscheide mich für geschützte Destruktoren in Schnittstellen.
Doc

4

Sie können auch Vertragsklassen berücksichtigen, die mit dem NVI (Non Virtual Interface Pattern) implementiert wurden. Zum Beispiel:

struct Contract1 : boost::noncopyable
{
    virtual ~Contract1();
    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:
    virtual void do_f(Parameters p); // From contract 1.
    virtual void do_g(Parameters p); // From contract 2.
};

Für andere Leser wird in diesem Artikel von Dr. Dobbs "Conversations: Virtually Yours" von Jim Hyslop und Herb Sutter etwas näher erläutert, warum man den NVI verwenden möchte.
user2067021

Und auch dieser Artikel "Virtuality" von Herb Sutter.
user2067021

1

Ich bin noch neu in der C ++ - Entwicklung. Ich habe mit Visual Studio (VS) begonnen.

Dennoch scheint niemand das __interfacein 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üssen virtual TYPE Method() = 0;, da es automatisch konvertiert wird.

__interface IMyInterface {
   HRESULT CommitX();
   HRESULT get_X(BSTR* pbstrName);
};

Ich verwende es jedoch nicht, da ich Bedenken hinsichtlich der plattformübergreifenden Kompilierungskompatibilität habe, da es nur unter .NET verfügbar ist.

Wenn jemand etwas Interessantes daran hat, teilen Sie es bitte mit. :-)

Vielen Dank.


0

Es ist zwar wahr, dass dies virtualder 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:

struct IButton
{
    void (*click)(); // might be std::function(void()) if you prefer

    IButton( void (*click_)() )
    : click(click_)
    {
    }
};

// call as:
// (button.*click)();

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:

  • Sie können davon als Basisklasse erben (sowohl virtuelle als auch nicht virtuelle sind zulässig) und clickden Konstruktor Ihres Nachkommens ausfüllen .
  • Möglicherweise haben Sie den Funktionszeiger als protectedMitglied und eine publicReferenz und / oder einen Getter.
  • Wie oben erwähnt, können Sie auf diese Weise die Implementierung zur Laufzeit wechseln. Somit ist es auch eine Möglichkeit, den Staat zu verwalten. Abhängig von der Anzahl der ifÄnderungen von s gegenüber dem Status in Ihrem Code ist dies möglicherweise schneller als switch()es oder ifs (der Turnaround wird um 3-4 ifs erwartet , aber messen Sie immer zuerst.
  • Wenn Sie std::function<>Funktionszeiger auswählen , können Sie möglicherweise alle Ihre Objektdaten darin verwalten IBase. Ab diesem Punkt können Sie Wertschemata für haben IBase(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 im std::function<>Vergleich zu Funktionszeigern oder sogar virtuellen Funktionen tendenziell einen Overhead (dies könnte sich in Zukunft ändern).

0

Hier ist die Definition von abstract classin c ++ Standard

n4687

13.4.2

Eine abstrakte Klasse ist eine Klasse, die nur als Basisklasse einer anderen Klasse verwendet werden kann. Es können keine Objekte einer abstrakten Klasse erstellt werden, außer als Unterobjekte einer davon abgeleiteten Klasse. Eine Klasse ist abstrakt, wenn sie mindestens eine reine virtuelle Funktion hat.


-2
class Shape 
{
public:
   // pure virtual function providing interface framework.
   virtual int getArea() = 0;
   void setWidth(int w)
   {
      width = w;
   }
   void setHeight(int h)
   {
      height = h;
   }
protected:
    int width;
    int height;
};

class Rectangle: public Shape
{
public:
    int getArea()
    { 
        return (width * height); 
    }
};
class Triangle: public Shape
{
public:
    int getArea()
    { 
        return (width * height)/2; 
    }
};

int main(void)
{
     Rectangle Rect;
     Triangle  Tri;

     Rect.setWidth(5);
     Rect.setHeight(7);

     cout << "Rectangle area: " << Rect.getArea() << endl;

     Tri.setWidth(5);
     Tri.setHeight(7);

     cout << "Triangle area: " << Tri.getArea() << endl; 

     return 0;
}

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.


5
Dies wird nicht als Schnittstelle angesehen! Das ist nur eine abstrakte Basisklasse mit einer Methode, die überschrieben werden muss! Schnittstellen sind normalerweise Objekte, die nur Methodendefinitionen enthalten - ein "Vertrag", den andere Klassen erfüllen müssen, wenn sie die Schnittstelle implementieren.
Gitarrenfluss
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.