2 Stimmen

Virtuelle Vererbung - Überspringen von Konstruktoren

Ich habe die folgenden Klassen:

class Socket
{
    Socket();
    Socket( SOCKET s );
};

class Connection : public virtual Socket
{
    Connection( IP ip );
};

Diese beiden Klassen enthalten einige rein virtuelle Funktionen und einige nicht virtuelle Funktionen und einige eigene Daten. Der Sinn dahinter ist, dass ich einige Socket-Typen ableite, die verschiedene Protokolle implementieren.

Also spezialisiere ich auf diese beiden Klassen:

class ProtocolSocket : public virtual Socket
{
    ProtocolSocket() {}
    ProtocolSocket( SOCKET s ) : Socket( s ) { ; }
};

class ProtocolConnection : public ProtocolSocket, public virtual Connection
{
    ProtocolConnection( SOCKET s, IP ip ) : ProtocolSocket( s ), Connection( ip ) {;}
};

Etwas geht schief - wie sicher viele von Ihnen sehen können. Ich versuche, eine ProtocolConnection zu erstellen:

new ProtocolConnection( s, ip );

Der Aufbau erfolgt wie folgt:

start ctor ProtocolConnection
    start ctor Connection
       start ctor Socket
          Socket(); - Standard ctor über die Init-Liste von Connection
       end ctor Socket
       Connection(); - Standard ctor der Init-Liste von ProtocolConnection
    end ctor Connection
    start ctor ProtocolSocket
       start ctor Socket     
          // Socket( s ); - übersprungen!!! - hätte von der Init-Liste von ProtocolSocket kommen sollen, aber der Konstruktor für dieses Objekt wurde bereits aufgerufen!
       end ctor Socket
       ProtocolSocket( s ); - von der Init-Liste von ProtocolConnection()
    end ctor ProtocolSocket
    ProtocolConnection( s, ip );
end ctor ProtocolConnection

Das Überspringen dieses zweiten Socket-Konstruktors ist das, was die Sprachspezifikation besagt, dass es passieren soll, und zwar aus guten Gründen.

Wie rufe ich den Konstruktor mit Socket( s ) auf, anstatt den früheren?

Ich beabsichtige, mehrere abgeleitete Klassen z.B. Auch OtherProtocolSocket und OtherProcolConnection, auf derselben Ebene wie ProtocoSocket und ProtocolConnection-Objekte zu haben.

Der Effekt, den ich zu erzielen versuche, ist, dass ich ProtocolSocket- und ProtocolConnection-Objekte erstelle und dann als Socket- und Connection-Objekte in meinem System übergebe. Nachdem ich also ein Socket erstellt habe, das ein bestimmtes Protokoll implementiert, lese und schreibe ich einfach darauf, ohne mich um die Details des zugrunde liegenden Protokolls kümmern zu müssen.

Connection-Objekte müssen alle Methoden von Socket-Objekten erben.

@UPDATE:

DyP schlägt vor, einen Initialisierer für Socket in ProtocolConnection hinzuzufügen. Das löst das Problem. Ich würde Ihnen dafür eine Akzeptanz geben... aber es war nur in einem Kommentar.

4voto

Dave S Punkte 19802

Der Schlüssel zu erinnern ist, dass Konstruktoren für virtuelle Basisklassen als Teil der Initialisierung der am meisten abgeleiteten Klasse erfolgen (und vor der Konstruktion der anderen Basisklassen). Also ist Ihre Reihenfolge der Konstruktion nicht richtig.

Tatsächlich, was passiert, wenn Sie den ProtocolConnection konstruieren, ist, dass es zuerst den Socket konstruiert, gefolgt von der Connection (da Sie diese virtuell geerbt haben), und schließlich den Protokoll-Socket.

Um den gewünschten Konstruktor des Sockets aufzurufen, müssen Sie seinen Konstruktor als Teil der ProtocolSocket-Mitgliedsinitialisierungsliste aufrufen, wie folgt

class ProtocolConnection: public ProtocolSocket, public virtual Connection
{
    public:
    ProtocolConnection(int s, int ip) :
        Socket(s), Connection(ip), ProtocolSocket(s)  
        // Hinweis, auch neu angeordnet, da alle virtuellen Basen vor den nicht-virtuellen Basen initialisiert werden
    {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }
};

Zuletzt, als Hinweis, würde ich empfehlen, Ihre Vererbungshierarchie zu vereinfachen. Insbesondere die virtuelle Vererbung und die Verwendung mehrerer Konstruktoren machen die Dinge komplizierter.

2voto

dyp Punkte 37134

Vererbungs-DAG:

       ProtocolConnection
           /        \
     non-virtual  virtual
         /            \
ProtocolSocket     Connection
       |               |
    virtual         virtual
       |               |
    Socket           Socket

Es gibt nur ein Socket-Teilobjekt in einem Objekt vom Typ ProtocolConnection aufgrund der virtuellen Vererbung.

[class.base.init]/10

Zuerst werden, und nur für den Konstruktor der am meisten abgeleiteten Klasse (1.8), virtuelle Basisklassen in der Reihenfolge initialisiert, in der sie bei einer Tiefensuche von links nach rechts im azyklischen gerichteten Graphen der Basisklassen erscheinen, wobei "von links nach rechts" die Reihenfolge des Auftretens der Basisklassen in der basisspezifizierenden Liste der abgeleiteten Klasse ist.

Die Initialisierung von virtuellen Basisklassen erfolgt über eine Tiefensuche von links nach rechts. Die Reihenfolge der Durchquerung:

       (0) ProtocolConnection
             /             \
           nv               v
           /                 \
(1) ProtocolSocket    (3) Connection
         |                   |
         v                   nv
         |                   |
    (2) Socket         (4) Socket

Führt zu einer Initialisierungsreihenfolge von:

(2); (3); (1); (0)
Socket; Connection; ProtocolSocket (nicht-virtuelle Basisklasse); ProtocolConnection

Die am meisten abgeleitete Klasse ProtocolConnection muss die Initialisierer für alle virtuellen Basisklassen einschließen. Wenn eine virtuelle Basisklasse nicht in der mem-initializer-list der am meisten abgeleiteten Klasse erscheint, wird das Teilobjekt dieser virtuellen Basisklasse standardmäßig konstruiert.

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