2009-08-01 16 views
31

Da C++ die interface-Funktion von Java und C# fehlt, was ist die bevorzugte Möglichkeit, Schnittstellen in C++ - Klassen zu simulieren? Meine Vermutung wäre eine mehrfache Vererbung von abstrakten Klassen. Welche Auswirkungen hat der Speicher-Overhead/die Leistung? Gibt es Namenskonventionen für solche simulierten Schnittstellen, z. B. SerializableInterface?Wie kann ich Schnittstellen in C++ simulieren?

+9

Fehler Schnittstelle. Das scheint eine negative Formulierung zu sein. C++ hat keine Schnittstellen (die Klasse ist eine Schnittstelle), es fehlt nur die Schlüsselwortschnittstelle, weil es nicht verkümmert ist. –

+3

Das Schlüsselwort Interface enthält garantiert keinen Code oder Daten, so dass sie ohne Probleme mit anderen Schnittstellen interagieren können. Es gibt keine Möglichkeit, eine solche Garantie in C++ zu geben, man muss nur hoffen, dass sie nichts tun, was zu Konflikten führt. Eine Menge Dinge, die Java und C# für die Lesbarkeit, Interoperabilität und Verständlichkeit von Code tun, wurden als Reaktion auf Probleme, die bei der Arbeit in C++ auftraten, herausgefunden. –

+2

möglich Duplikat von [Wie deklariert man eine Schnittstelle in C++?] (Http://stackoverflow.com/questions/318064/how-do-you-declare-an-interface-in-c) –

Antwort

29

Da C++ im Gegensatz zu C# und Java mehrere Vererbungen aufweist, können Sie eine Reihe von abstrakten Klassen erstellen.

Wie für die Konvention, es liegt an Ihnen; Allerdings möchte ich mit einem I.

class IStringNotifier 
{ 
public: 
    virtual void sendMessage(std::string &strMessage) = 0; 
    virtual ~IStringNotifier() { } 
}; 

Die Leistung ist nichts, um die Klassennamen vorangestellt in Bezug auf Vergleich zwischen C# und Java zu befürchten. Im Grunde haben Sie nur den Aufwand, eine Nachschlagetabelle für Ihre Funktionen oder eine Vtable zu haben, genau wie jede Art von Vererbung mit virtuellen Methoden.

+5

Und machen Sie die abstrakte Klasse Destruktor virtual, wenn Sie das Objekt mit einem Zeiger auf die abstrakte Klasse löschen möchten. –

+0

@ Jim Huang: Zustimmen hinzugefügt, um explizit zu sein. –

+9

@Michael Aaron Safyan: Danke für den Downvote, vielleicht wäre es besser, wenn du die Fragen nach dem beurteilen würdest, was sie antworteten, und nicht deine Vorliebe für den Programmierstil? –

3

Schnittstellen in C++ sind Klassen, die nur reine virtuelle Funktionen haben. Z.B. :

class ISerializable 
{ 
public: 
    virtual ~ISerializable() = 0; 
    virtual void serialize(stream& target) = 0; 
}; 

Dies ist nicht eine simulierte Schnittstelle, es ist eine Schnittstelle wie die, die in Java, aber die Nachteile nicht tragen.

z. Sie können Methoden und Member ohne negative Konsequenzen hinzufügen:

class ISerializable 
{ 
public: 
    virtual ~ISerializable() = 0; 
    virtual void serialize(stream& target) = 0; 
protected: 
    void serialize_atomic(int i, stream& t); 
    bool serialized; 
}; 

Zu den Namenskonventionen ... gibt es keine echten Namenskonventionen, die in der Sprache C++ definiert sind. Wähle also den in deiner Umgebung.

Der Overhead ist 1 statische Tabelle und in abgeleiteten Klassen, die noch keine virtuellen Funktionen hatten, ein Zeiger auf die statische Tabelle.

+1

Ich glaube nicht, dass Sie virtuelle Konstruktoren haben können. Sie können jedoch virtuelle Destruktoren haben. – jkeys

+0

@hooked, Tippfehler behoben. – Christopher

+1

Ich sehe keinen Grund für Destruktor rein virtuell sein, einfach nur virtuell wäre genug. Auch deklariert dtor ist nicht genug, es muss definiert werden. Im Fall von reinem virtuellem dtor muss es außerhalb der Klassendefinition wie folgt definiert werden: ISerializable :: ~ ISerializable() {} , da C++ - Grammatik nicht sowohl rein virtuellen Spezifizierer und Memberfunktion in Klasse ermöglicht Definition. –

1

Wenn Sie keine virtuelle Vererbung verwenden, sollte der Overhead nicht schlechter sein als die reguläre Vererbung mit mindestens einer virtuellen Funktion. Jede davon abgeleitete abstrakte Klasse fügt jedem Objekt einen Zeiger hinzu.

Wenn Sie jedoch so etwas wie die Leere Basisklasse Optimierung tun, können Sie, dass minimieren:

 
struct A 
{ 
    void func1() = 0; 
}; 

struct B: A 
{ 
    void func2() = 0; 
}; 

struct C: B 
{ 
    int i; 
}; 

Die Größe C werden zwei Worte.

7

"Welche Auswirkungen hat der Speicher-Overhead/die Leistung?"

Normalerweise keine außer denen, die virtuelle Anrufe überhaupt verwenden, obwohl vom Standard hinsichtlich der Leistung nicht viel garantiert wird.

Bei Speicher-Overhead ermöglicht die Optimierung der "leeren Basisklasse" dem Compiler ausdrücklich, Strukturen so zu strukturieren, dass das Hinzufügen einer Basisklasse ohne Datenelemente die Größe Ihrer Objekte nicht erhöht. Ich denke, Sie werden wahrscheinlich nicht mit einem Compiler zu tun haben, der das nicht tut, aber ich könnte falsch liegen.

Das Hinzufügen der ersten virtuellen Elementfunktion zu einer Klasse erhöht normalerweise Objekte um die Größe eines Zeigers, verglichen mit dem, wenn sie keine virtuellen Elementfunktionen hatten. Das Hinzufügen weiterer virtueller Elementfunktionen macht keinen zusätzlichen Unterschied. Das Hinzufügen von virtuellen Basisklassen könnte einen weiteren Unterschied machen, aber für das, worüber Sie sprechen, brauchen Sie das nicht.

Das Hinzufügen mehrerer Basisklassen mit virtuellen Memberfunktionen bedeutet wahrscheinlich, dass Sie die leere Basisklassenoptimierung nur einmal erhalten, da das Objekt in einer typischen Implementierung mehrere vtable-Zeiger benötigt. Wenn Sie also mehrere Interfaces für jede Klasse benötigen, fügen Sie möglicherweise die Größe der Objekte hinzu.

Auf die Leistung hat ein virtueller Funktionsaufruf ein kleines bisschen mehr Aufwand als ein nicht-virtueller Funktionsaufruf, und noch wichtiger, Sie können davon ausgehen, dass es im Allgemeinen (immer?) Nicht inline wird. Das Hinzufügen einer leeren Basisklasse fügt der Konstruktion oder der Destruktion in der Regel keinen Code hinzu, da der leere Basiskonstruktor und Destruktor in den Konstruktor/Destruktor-Code der abgeleiteten Klasse eingebunden werden können.

Es gibt Tricks, die Sie verwenden können, um virtuelle Funktionen zu vermeiden, wenn Sie explizite Schnittstellen wünschen, aber keinen dynamischen Polymorphismus benötigen. Wenn Sie jedoch versuchen, Java zu emulieren, nehme ich an, dass dies nicht der Fall ist.

Beispielcode:

#include <iostream> 

// A is an interface 
struct A { 
    virtual ~A() {}; 
    virtual int a(int) = 0; 
}; 

// B is an interface 
struct B { 
    virtual ~B() {}; 
    virtual int b(int) = 0; 
}; 

// C has no interfaces, but does have a virtual member function 
struct C { 
    ~C() {} 
    int c; 
    virtual int getc(int) { return c; } 
}; 

// D has one interface 
struct D : public A { 
    ~D() {} 
    int d; 
    int a(int) { return d; } 
}; 

// E has two interfaces 
struct E : public A, public B{ 
    ~E() {} 
    int e; 
    int a(int) { return e; } 
    int b(int) { return e; } 
}; 

int main() { 
    E e; D d; C c; 
    std::cout << "A : " << sizeof(A) << "\n"; 
    std::cout << "B : " << sizeof(B) << "\n"; 
    std::cout << "C : " << sizeof(C) << "\n"; 
    std::cout << "D : " << sizeof(D) << "\n"; 
    std::cout << "E : " << sizeof(E) << "\n"; 
} 

Output (GCC auf einer 32-Bit-Plattform):

A : 4 
B : 4 
C : 8 
D : 8 
E : 12 
5

gibt es wirklich keine Notwendigkeit zu 'simulieren' alles wie es nicht, dass C++ etwas fehlt, dass Java kann mit Schnittstellen machen.

Von einem C++ - Zeiger der Ansicht macht Java eine "künstliche" Unterscheidung zwischen einem interface und einem class. Ein interface ist nur ein class, deren Methoden abstrakt sind und die keine Datenelemente enthalten können.

Java macht diese Einschränkung, da es keine unbeschränkte Mehrfachvererbung zulässt, aber es ermöglicht eine class bis implement mehrere Schnittstellen.

In C++ ist eine class eine class und eine interface ist eine class. extends wird durch öffentliche Vererbung erreicht und implements wird auch durch öffentliche Vererbung erreicht.

Die Übernahme von mehreren Nicht-Interface-Klassen kann zu zusätzlichen Komplikationen führen, kann jedoch in einigen Situationen nützlich sein. Wenn Sie sich darauf beschränken, nur Klassen von höchstens einer Nicht-Interface-Klasse und einer beliebigen Anzahl vollständig abstrakter Klassen zu erben, werden Sie keine anderen Schwierigkeiten haben als in Java (andere C++/Java-Unterschiede ausgenommen).

In Bezug auf die Speicher- und Gemeinkosten, wenn Sie eine Java-Klassenhierarchie neu erstellen, dann haben Sie wahrscheinlich die virtuellen Funktionskosten für Ihre Klassen in jedem Fall bereits bezahlt. Angesichts der Tatsache, dass Sie ohnehin verschiedene Laufzeitumgebungen verwenden, wird es keinen grundlegenden Unterschied zwischen den beiden geben, was die Kosten der verschiedenen Vererbungsmodelle angeht.

1

Übrigens hat MSVC 2008 __interface Schlüsselwort.

A Visual C++ interface can be defined as follows: 

- Can inherit from zero or more base 
    interfaces. 
- Cannot inherit from a base class. 
- Can only contain public, pure virtual 
    methods. 
- Cannot contain constructors, 
    destructors, or operators. 
- Cannot contain static methods. 
- Cannot contain data members; 
    properties are allowed. 

Diese Funktion ist Microsoft Specific. Vorsicht: __interface hat keinen virtuellen Destruktor, der erforderlich ist, wenn Sie Objekte mit seinen Schnittstellenzeigern löschen.

+0

Ich denke, das ist mehr die COM/DCOM-Art, C++ zu machen. –

+0

Warum denkst du das? –

+0

Nur weil es die einzigen Orte sind wo ich es gesehen habe ;-), wenn ich mit COM Interfaces arbeite. –

1

In C++ können wir weiter gehen als die einfachen verhaltenslosen Schnittstellen von Java & co. Wir können explizite Verträge (wie in Design by Contract) mit dem NVI-Muster hinzufügen.

struct Contract1 : noncopyable 
{ 
    virtual ~Contract1(); 
    Res f(Param p) { 
     assert(f_precondition(p) && "C1::f precondition failed"); 
     const Res r = do_f(p); 
     assert(f_postcondition(p,r) && "C1::f postcondition failed"); 
     return r; 
    } 
private: 
    virtual Res do_f(Param p) = 0; 
}; 

struct Concrete : virtual Contract1, virtual Contract2 
{ 
    ... 
}; 
0

Es gibt keine gute Möglichkeit, eine Schnittstelle so zu implementieren, wie Sie es wünschen. Das Problem bei einem Ansatz wie der vollständig abstrakten ISerializable-Basisklasse liegt in der Art und Weise, wie C++ die Mehrfachvererbung implementiert. Beachten Sie Folgendes:

class Base 
{ 
}; 
class ISerializable 
{ 
    public: 
    virtual string toSerial() = 0; 
    virtual void fromSerial(const string& s) = 0; 
}; 

class Subclass : public Base, public ISerializable 
{ 
}; 

void someFunc(fstream& out, const ISerializable& o) 
{ 
    out << o.toSerial(); 
} 

eindeutig die Absicht, für die Funktion toSerial() ist allen Mitgliedern der Unterklasse einschließlich der Serialisierung, die sie von Basisklasse erbt. Das Problem ist, dass es keinen Pfad von ISerializable zu Base gibt. Sie können dies grafisch sehen, wenn Sie die folgenden ausführen:

void fn(Base& b) 
{ 
    cout << (void*)&b << endl; 
} 
void fn(ISerializable& i) 
{ 
    cout << (void*)&i << endl; 
} 

void someFunc(Subclass& s) 
{ 
    fn(s); 
    fn(s); 
} 

Der Wert, ausgegeben durch den ersten Anruf nicht der gleiche wie der Wert, ausgegeben durch den zweiten Anruf. Obwohl in beiden Fällen ein Verweis auf s übergeben wird, passt der Compiler die übergebene Adresse so an, dass sie dem richtigen Basisklassentyp entspricht.

Verwandte Themen