5 Stimmen

Schnittstellen in C++

Ich würde gerne Schnittstellen in C++ wie in Java oder C# verwenden. Ich habe beschlossen, rein abstrakte Klassen mit Mehrfachvererbung zu verwenden, aber irgendetwas ist furchtbar falsch, wenn ich die Schnittstelle spezialisiere:

class Interface
{
public:
  virtual int method() = 0;
};

// Default implementation.
class Base: virtual public Interface
{
public:
  virtual int method() {return 27;}
};

// specialized interface
class Interface2: public Interface
{
public:
  virtual int method() = 0;
// some other methods here
};

// concrete class - not specialised - OK
class Class: public virtual Interface, public virtual Base
{
};

// concrete class - specialised
class Class2: public Interface2, public Base
{
};

int main()
{
  Class c;
  Class2 c2;
return 0;
}

Warnung 1 Warnung C4250: 'Klasse' : erbt 'Base::Base::method' über Dominanz 30

Fehler 2 Fehler C2259: 'Klasse2': kann abstrakte Klasse nicht instanziieren 42

Wie geht man dabei richtig vor?

5voto

Tom Punkte 41522

Klasse2 erbt von einer abstrakten Klasse (Interface2), implementiert aber die rein virtuelle Methode nicht, so dass sie eine abstrakte Klasse bleibt.

4voto

Pieter Punkte 16973

Auf der Grundlage dieses Kommentars

If the method is not reimplemented in Class2 or Class (it is not in 
this case) Base::method() will be called. Otherwise the reimplementation 
will be called. There is an interface hierarchy with a common base 
dumb implementation. 

– danatel 16 mins ago

Das ist nicht das, was Sie haben, Sie haben keine gemeinsame Basis, Sie haben

Interface -> Interface2 -> Class2
Interface -> Base -> Class2

Die Schnittstelle wird im Ableitungsbaum nicht "verschmolzen", interface2 erbt nicht virtuell von interface, sondern hat seine eigene Schnittstellen-Superklasse. Es ist wie die rein virtuelle method() existiert zweimal in Klasse2, einmal implementiert über Klasse und einmal nicht implementiert.

Und selbst wenn Sie virtuell geerbt hätten, würde die gemeinsame Basis (Schnittstelle) immer noch keine Implementierung haben

Wenn Base triviale Operationen enthält, die in der gesamten Hierarchie verwendbar sein sollten, warum dann nicht Base als Startpunkt nehmen? (auch wenn sie noch rein virtuell mit einer Implementierung ist).

Wenn dies nur ein sehr einfaches Beispiel wäre, um die Frage kurz zu machen, etwas wie die Brücke Muster könnte nützlicher sein. Aber es ist schwer, Sie weiter zu beraten, ohne mehr zu wissen.

4voto

markh44 Punkte 5498

Ha, ha, dieses Problem kitzelt etwas, das irgendwo tief in meinem Kopf vergraben ist. Ich kann es nicht genau benennen, aber ich denke, es hat mit der Definition einer Schnittstellenhierarchie und der Vererbung sowohl einer Schnittstelle als auch einer Implementierung zu tun. Man vermeidet dann, dass man alle Funktionen mit implementieren muss, indem man Aufrufe an eine Basisklasse weiterleitet. Denke ich.

Ich denke, dieses einfache Beispiel zeigt dasselbe, ist aber vielleicht etwas leichter zu verstehen, weil es Dinge verwendet, die leicht zu visualisieren sind: (bitte verzeihen Sie die Strukturfaulheit)

#include <iostream>
using namespace std;

struct Vehicle
{
    virtual void Drive() = 0;
};

struct VehicleImp : virtual public Vehicle
{
    virtual void Drive() 
    {
        cout << "VehicleImp::Drive\n";
    }
};

struct Tank : virtual public Vehicle
{ 
    virtual void RotateTurret() = 0;
};

struct TankImp : public Tank, public VehicleImp
{
    virtual void RotateTurret() 
    {
        cout << "TankImp::RotateTurret\n";
    }
    // Could override Drive if we wanted
};

int _tmain(int argc, _TCHAR* argv[])
{
    TankImp myTank;
    myTank.Drive();         // VehicleImp::Drive
    myTank.RotateTurret();  // TankImp::RotateTurret
    return 0;
}

TankImp hat im Wesentlichen die Schnittstelle Tank und die Implementierung Vehicle geerbt.

Nun, ich bin mir ziemlich sicher, dass dies in OO-Kreisen eine bekannte und akzeptable Sache ist (aber ich weiß nicht, ob es einen schicken Namen hat), so dass die gefürchtete Diamant-Sache in diesem Fall in Ordnung ist, und Sie können die Dominanz-Warnung sicher unterdrücken, weil es das ist, was Sie in diesem Fall passieren wollen.

Ich hoffe, das hilft Ihnen, die richtige Richtung einzuschlagen!

Übrigens wurde Ihr Code nicht kompiliert, weil Sie die rein virtuelle "Methode" in Klasse2 nicht implementiert hatten.

EDIT:

Ok, ich glaube, ich verstehe Ihr Problem jetzt besser, und ich denke, der Fehler liegt in Interface2. Versuchen Sie, es in dieses zu ändern:

// specialized interface
class Interface2: public virtual Interface // ADDED VIRTUAL
{
public:
    //virtual int method() = 0;   COMMENTED THIS OUT
    // some other methods here
};

Interface2 sollte nicht die rein virtuelle Definition der Methode haben, da diese bereits in Interface enthalten ist.

Die Vererbung von Interface muss virtuell sein, sonst gibt es eine Mehrdeutigkeit mit Base::method, wenn man von Interface2 und Base in Class2 ableitet.

Jetzt sollten Sie feststellen, dass es kompiliert wird, möglicherweise mit Dominanzwarnungen, und wenn Sie c2.method() aufrufen, erhalten Sie 27.

3voto

j_random_hacker Punkte 49159

Betreffend Class : Alles, was Sie tun müssen, ist Folgendes abzuleiten Class von Base -- die Tatsache, dass es die Interface ist implizit und in der Tat unausweichlich:

class Class: public Base  // virtual inheritance is unnecessary here
{
};

Class wird erben method() von Base wie gewünscht.

Betreffend Class2 :

Haftungsausschluss: Negatives Ergebnis voraus

Ausgehend von Ihrem Kommentar zu Toms Antwort , I Ich dachte, ich hätte die Antwort für Class2 :

// concrete class - specialised
class Class2: public Interface2, public Base
{
public:
    using Base::method;    // "Imports" all members named "method" from Base
};

Aber eigentlich funktioniert das nicht. Ein Blick in den C++-Standard zeigt, dass Abschnitt 7.3.3, Absatz 14 erklärt, dass using kann nicht verwendet werden, um zweideutige Zugriffe auf geerbte Mitglieder aufzulösen:

... [Hinweis: Da eine using-Deklaration ein Mitglied der Basisklasse bezeichnet (und nicht ein Unterobjekt oder eine Funktion eines Unterobjekts der Basisklasse), kann eine using-Deklaration nicht verwendet werden, um Mehrdeutigkeiten bei geerbten Mitgliedern aufzulösen. ...]

Es scheint, dass die einzige Möglichkeit, das gewünschte Verhalten in Class2 ist die manuelle Weiterleitung der Methode:

// concrete class - specialised
class Class2: public Interface2, public Base
{
public:
     virtual int method() { return Base::method(); }
};

Betreffend virtual Vererbung: Sie nicht brauchen es für Class Erklärung, aber Sie haben wahrscheinlich tun brauchen es für Interface2 Erklärung, um sicherzustellen, dass Class2 hat nur ein einziges Unterobjekt vom Typ Interface -- so wie es aussieht, wird jeder Class2 Objekt hat zwei Unterobjekte dieses Typs. (Obwohl das keine Probleme verursachen wird, wenn Interface ist in der Tat eine reine Schnittstelle, die keine Mitgliedsvariablen hat). Wenn es hilft, zeichnen Sie ein Diagramm: jedes Mal, wenn eine Basisklasse ohne das Schlüsselwort virtual erscheint sie als eigenständiges Objekt; alle Basisklassen, die mit dem Schlüsselwort virtual werden zu einem Objekt zusammengefasst.

[UPDATE: markh44s ausgezeichnete Antwort zeigt, dass der oben beschriebene Ansatz (die Herstellung Interface2 erben praktisch von Interface ) wird in der Tat erlauben Class2 um automatisch die Implementierung von method() von Base ! Problem gelöst!]

3voto

Steven Punkte 3788

Sie sollten auch die Definition eines virtuellen Destruktors in Ihrer Schnittstelle in Betracht ziehen, wenn Sie mit einem Schnittstellen- oder Basiszeiger löschen möchten.

Ohne einen virtuellen Destruktor werden Sie Probleme bekommen, wenn Sie etwas tun wie:

Base *b = new Class2();
delete b;

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