[c++] Wie deklariert man eine Schnittstelle in C ++?



Answers

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

Eine reine 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
        }
};
Question

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




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 hat ein Interface Referenzsemantik, dh Sie müssen sicherstellen, dass das Objekt die Schnittstelle überdauert (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 in Typen durch, die keine Typen mit virtuellen Funktionen haben / erben).

Schließlich ist Vererbung die Wurzel allen Übels im komplexen Softwaredesign. In Sean Parents Wert Semantik und Konzepte-basierten Polymorphismus (sehr empfehlenswert, bessere Versionen dieser Technik werden dort erklärt) der folgende Fall wird untersucht:

Angenommen, ich habe eine Anwendung, in der ich meine Formen polymorph unter Verwendung der MyShape Oberfläche MyShape :

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

In Ihrer Anwendung machen Sie das gleiche mit verschiedenen Formen, die die YourShape Schnittstelle verwenden:

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

Jetzt sagen Sie, dass Sie einige der Formen verwenden möchten, die ich in Ihrer Anwendung entwickelt habe. Konzeptionell haben unsere Shapes die gleiche Oberfläche, aber damit meine Shapes in Ihrer Anwendung funktionieren, müssten Sie meine Shapes 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. Darüber hinaus führt die Mehrfachvererbung zu Spaghetti-Code (stellen Sie sich vor, ein drittes Projekt kommt mit der TheirShape Schnittstelle ... was passiert, wenn sie auch ihre my_draw ?).

Update: Es gibt ein paar neue Referenzen über Nicht-Vererbungs-Polymorphie:




Während es stimmt, dass virtual der De-facto-Standard ist, um eine Schnittstelle zu definieren, vergessen wir nicht das klassische C-ähnliche Muster, das mit einem Konstruktor in C ++ kommt:

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 Ereignislaufzeit erneut binden können, ohne Ihre Klasse neu konstruieren zu müssen (da C ++ keine Syntax zum Ändern von polymorphen Typen hat, ist dies eine Umgehung für Chamäleon-Klassen).

Tipps:

  • Sie können davon als eine Basisklasse erben (sowohl virtuelle als auch nicht-virtuelle sind erlaubt) und füllen click den Konstruktor Ihres Nachkommens.
  • Sie können den Funktionszeiger als ein protected Mitglied haben und eine public Referenz und / oder Getter haben.
  • Wie oben erwähnt, können Sie damit die Implementierung in Runtime wechseln. So ist es auch eine Möglichkeit, den Staat zu verwalten. Abhängig von der Anzahl der Änderungen von if s im Vergleich zum Status in Ihrem Code ist dies möglicherweise schneller als switch() es oder if s (der Turnaround wird bei 3-4 erwartet, aber immer zuerst messen.
  • Wenn Sie std::function<> über Funktionszeiger wählen, können Sie möglicherweise alle Ihre Objektdaten in IBase . Von diesem Punkt aus können Sie IBase für IBase (zB 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 std::function<> tendenziell einen Overhead im Vergleich zu Funktionszeigern oder sogar virtuellen Funktionen (dies könnte sich in der Zukunft ändern).



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

  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 die virtuelle Vererbung, um Probleme mit mehrfacher Vererbung zu vermeiden. (Es gibt häufiger Mehrfachvererbung, wenn wir Schnittstellen verwenden.)

Und wie andere Antworten:

  • Machen 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
            }
    }
    



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

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

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

Allerdings benutze ich es nicht, da ich Bedenken wegen der plattformübergreifenden Kompilierungskompatibilität habe, da es nur unter .NET verfügbar ist.

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

Vielen Dank.




Es gibt kein Konzept von "Schnittstelle" per se in C ++. AFAIK, Schnittstellen wurden erstmals in Java eingeführt, um den Mangel an Mehrfachvererbung zu umgehen. Dieses Konzept hat sich als sehr nützlich erwiesen, und der gleiche Effekt kann in C ++ durch Verwendung einer abstrakten Basisklasse erreicht werden.

Eine abstrakte Basisklasse ist eine Klasse, in der mindestens eine Elementfunktion (Methode in Java-Jargon) eine reine virtuelle Funktion ist, die mit folgender 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 foo() bereitstellt, wird ebenfalls 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 als eine Schnittstelle sein kann, da sie Datenelemente und Memberfunktionen enthalten kann, die nicht rein virtuell sind. Ein Äquivalent einer Schnittstelle wäre eine abstrakte Basisklasse ohne Daten mit rein virtuellen Funktionen.

Und wie Mark Ransom bemerkte, sollte eine abstrakte Basisklasse einen virtuellen Destruktor bereitstellen, genau wie jede Basisklasse.




Ein kleiner Zusatz zu dem, was dort oben steht:

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

Zweitens möchten Sie möglicherweise virtuell (statt normalerweise) erben, wenn Sie das implementieren, nur für gute Maßnahmen.




Related