wake-up-neo.com

Wie deklarieren Sie eine Schnittstelle in C++?

Wie richte ich eine Klasse ein, die eine Schnittstelle darstellt? Ist das nur eine abstrakte Basisklasse?

757
Aaron Fischer

Um die Antwort um bradtgmurray zu erweitern, möchten Sie vielleicht eine Ausnahme in der Liste der rein virtuellen Methoden Ihrer Schnittstelle machen, indem Sie einen virtuellen Destruktor hinzufügen. Auf diese Weise können Sie den Zeiger auf eine andere Partei übertragen, ohne die konkrete abgeleitete Klasse verfügbar zu machen. 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 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 Rumpf für den virtuellen Destruktor hinzufügen - es stellt sich heraus, dass einige Compiler Schwierigkeiten haben, einen leeren Destruktor zu optimieren, und Sie sollten die Standardeinstellung besser verwenden. 

652
Mark Ransom

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

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

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

Der Grund, warum Sie zusätzlich zu abstrakten Basisklassen in C # / Java über eine spezielle Schnittstellentypkategorie verfügen, liegt darin, dass C #/Java keine Mehrfachvererbung unterstützt. 

C++ unterstützt die Mehrfachvererbung, sodass kein spezieller Typ benötigt wird. Eine abstrakte Basisklasse ohne nicht abstrakte (rein virtuelle) Methoden entspricht funktional einer C #/Java-Schnittstelle.

140
Joel Coehoorn

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

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

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

Eine abstrakte Basisklasse kann nicht instanziiert werden, d. H. e. 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() bereitstellt, ist auch abstrakt. Um nicht 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 Datenmitglieder 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 hervorgehoben hat, sollte eine abstrakte Basisklasse genau wie jede Basisklasse einen virtuellen Destruktor bereitstellen.

46
Dima

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

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

42
Carlos C Soto

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

  1. Deklarieren Sie einen virtuellen Destruktor in Ihrer Schnittstelle oder erstellen Sie einen geschützten nicht virtuellen, um undefiniertes Verhalten zu vermeiden, wenn jemand versucht, ein Objekt vom Typ IDemo zu löschen.

  2. Verwenden Sie virtuelle Vererbung, um Probleme mit 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
            }
    }
    
31
Rexxar

In C++ 11 können Sie 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 hat eine Schnittstelle eine Referenzsemantik, d. H. Sie müssen sicherstellen, dass das Objekt die Schnittstelle überlebt (es können auch Schnittstellen mit Wertesemantik erstellt werden). 

Diese Art von Schnittstellen hat ihre Vor- und Nachteile: 

Schließlich ist Vererbung die Wurzel allen Übels im komplexen Softwaredesign. In Sean Parent's Value Semantics and Concepts-based Polymorphism (sehr empfehlenswert, bessere Versionen dieser Technik werden dort erklärt), wird der folgende Fall untersucht:

Angenommen, ich habe eine Anwendung, in der ich polymorph mit meinen Formen über die Schnittstelle MyShape verarbeite:

struct MyShape { virtual void my_draw() = 0; };
struct Circle : MyShape { void my_draw() { /* ... */ } };
// more shapes: e.g. triangle

In Ihrer Anwendung machen Sie dasselbe mit verschiedenen Shapes unter Verwendung der YourShape-Schnittstelle:

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 die gleiche Schnittstelle. Damit meine Formen jedoch in Ihrer Anwendung funktionieren, müssen Sie sie wie folgt erweitern:

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

Erstens ist das Ändern meiner Formen möglicherweise überhaupt nicht möglich. Außerdem führt Mehrfachvererbung den Weg zu Spaghetti-Code (stellen Sie sich ein drittes Projekt vor, das die TheirShape-Schnittstelle verwendet ... was passiert, wenn sie auch ihre Zeichenfunktion my_draw? Nennen.).

Update: Es gibt einige neue Verweise auf nicht vererbungsbasierten Polymorphismus:

10
gnzlbg

Alle guten Antworten oben ... Eine weitere Sache, die Sie beachten sollten - Sie können auch einen reinen virtuellen Destruktor haben. Der einzige Unterschied ist, 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 Schnittstellenmethoden wie ich bereitstellen möchten, diese aber optional überschreiben. 

Um aus der Klasse eine Schnittstellenklasse zu machen, ist eine rein virtuelle Methode erforderlich. Alle virtuellen Methoden verfügen jedoch über Standardimplementierungen. Die einzige Methode, die als rein virtuell gilt, ist der Destruktor.

Einen Destruktor in der abgeleiteten Klasse neu zu implementieren ist keine große Sache - ich reimplementiere immer einen virtuellen oder nicht virtuellen Destruktor in meinen abgeleiteten Klassen.

9
Rodyland

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 */ }
}

Dieser Ansatz gefällt mir, weil er zu viel kleinerem Schnittstellencode führt und die generierte Codegröße erheblich geringer sein kann. Die Verwendung von novtable entfernt alle Verweise auf den vtable-Zeiger in dieser Klasse, sodass Sie ihn niemals direkt instanziieren können. Siehe die Dokumentation hier - novtable .

7
Mark Ingram

Eine kleine Ergänzung zu dem, was dort geschrieben wurde:

Stellen Sie zunächst sicher, dass Ihr Destruktor auch rein virtuell ist 

Zweitens möchten Sie möglicherweise virtuell (nicht normal) erben, wenn Sie die Implementierung durchführen, nur für gute Maßnahmen. 

4
Uri

Sie können auch mit NVI (Non Virtual Interface Pattern) implementierte Vertragsklassen berücksichtigen. 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.
};
4
Luc Hermitte

Ich bin noch in der Entwicklung von C++. Ich habe mit Visual Studio (VS) angefangen. 

Allerdings scheint niemand den __interface in VS (.NET) zu erwähnen. Ich bin nicht sehr sicher, ob dies eine gute Möglichkeit ist, eine Schnittstelle zu deklarieren. Es scheint jedoch eine zusätzliche Durchsetzung (in den Dokumenten erwähnt) zu geben. Damit Sie die virtual TYPE Method() = 0; nicht explizit angeben müssen, da sie automatisch konvertiert wird. 

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

Ich verwende es jedoch nicht, da ich mir Sorgen um die Kompatibilität mit plattformübergreifenden Kompilierungen mache, da es nur unter .NET verfügbar ist.

Wenn jemand etwas Interessantes darüber hat, bitte teilen. :-)

Vielen Dank.

1
Yeo

Zwar ist virtual der De-facto-Standard zur Definition einer Schnittstelle. Vergessen wir jedoch nicht das klassische C-ähnliche Muster, das in C++ mit einem Konstruktor 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 erneut binden können, ohne Ihre Klasse erneut erstellen zu müssen (da C++ keine Syntax für das Ändern polymorpher Typen hat, ist dies eine Problemumgehung für Chamäleonklassen).

Tipps:

  • Sie können davon als Basisklasse erben (es ist sowohl virtuell als auch nicht virtuell zulässig) und click im Konstruktor Ihres Nachkommens füllen.
  • Möglicherweise haben Sie den Funktionszeiger als protected-Member und eine public-Referenz und/oder einen Getter.
  • Wie oben erwähnt, können Sie die Implementierung in Runtime umschalten. Auf diese Weise können Sie auch den Status verwalten. Abhängig von der Anzahl von ifs im Vergleich zu Statusänderungen in Ihrem Code ist dieser möglicherweise schneller als switch()es oder ifs (Turnaround wird um 3-4 ifs erwartet, misst aber immer zuerst.
  • Wenn Sie std::function<> über Funktionszeiger wählen, können Sie möglicherweise alle Ihre Objektdaten in IBase verwalten. Ab diesem Punkt können Sie Wertschemata für IBase erstellen (z. B. std::vector<IBase> funktioniert). Beachten Sie, dass dieses möglicherweise langsamer ist, abhängig von Ihrem Compiler und dem STL-Code. auch, dass die derzeitigen Implementierungen von std::function<> im Vergleich zu Funktionszeigern oder sogar virtuellen Funktionen einen Overhead haben (dies kann sich in der Zukunft ändern).
0
lorro

Hier ist die Definition von abstract class im C++ - Standard

n4687

13.4.2

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

0
陳 力