Implizite Konstruktoren/Zuweisungsoperatoren



  • Hallo Forum,

    ich bin ein wenig verwirrt was implizit generierte Konstruktoren/Zuweisungsoperatoren angeht.
    Wenn keine Vererbung im Spiel ist, ist mir das klar, denke ich.

    Folgenden Fall verstehe ich dagegen nicht ganz.

    class Base
    {
    public:
        Base();
        virtual ~Base() = 0;
    
        void DoSomeStuff(){ /* do some stuff */ };
    }
    

    Wenn ich nun eine nicht abstrakte Ableitung haben möchte, muss ich den Destruktor implementieren und sei es nur als default-Variante.

    class Derived : public Base
    {
        Derived();
        virtual ~Derived() = default;
    
        void DoSomeStuff();
    }
    

    Wegen des implementierten Destruktors wird mir hier aber kein Move-Konstruktor implizit generiert, richtig?

    Da ich doch im Fall von Vererbung immer einen virtuellen Destruktor bereitstellen sollte, muss ich also spätestens in der ersten nicht abstrakten Ableitung immer Move-Konstruktor und Move-Zuweisung explizit implementieren (ggf. default).
    Wenn ich also Move-Support haben möchte, müsste meine Ableitung so aussehen.

    class Derived : public Base
    {
        Derived();
        virtual ~Derived() = default;
    
        Derived(Derived&&) = default;
        Derived& operator=(Derived&&) = default;
    
        void DoSomeStuff();
    }
    

    ...und am besten der Vollständigkeit halber auch noch Copy-Kontruktor und Zuweisungsoperator ebenfalls als default?

    Wäre nett, wenn mir das jemand bestätigen oder eben korrigieren könnte.
    Vielen Dank im Voraus!



  • "DoSomeStuff" war jetzt nicht ganz glücklich gewählt für das Beispiel.
    In der Basisklasse ebenfalls virtual und mit entsprechendem override-specifier in den Ableitungen wäre sinnvoller gewesen.



  • ...und das "public" in "Derived" fehlt natürlich ebenfalls, sorry!



  • Such mal nach "Rule of 3" bzw. seit C++11 auch "Rule of 5". Oder "Rule of zero".

    Im Prinzip: 0, 3 oder 5 Dinge implementieren.



  • AFAIK musst du alles deklarieren und das auch schon in der Basis.



  • Danke für die Hinweise! Inzwischen habe ich selbst nochmal ein wenig herumprobiert und recherchiert. Mein wesentlicher Denkfehler war der, dass der pure virtual Destruktor in der Basisklasse implementiert werden muss, wenn es zumindest eine konkrete Ableitung gibt.

    wob schrieb:

    "Rule of 3" bzw. seit C++11 auch "Rule of 5"

    Die "Rule of 3", "Rule of 5" oder auch "Rule of 4 1/2" ist mir schon bekannt. Die greift aber eigentlich dann, wenn die Default-Implementierung für eine Klasse eben nicht ausreicht.
    In der abstrakten Basisklasse ist die Default-Implementierung hingegen ausreichend und die Deklaration des Destruktors ist nur erforderlich, um die Klasse abstrakt zu machen (z.B. mangels anderer Methoden).
    Ist allerdings ein benutzerdefinierter Destruktor vorhanden, werden Move-Konstruktor und Move-Zuweisung nicht implizit generiert und meine Frage zielte darauf, wie man den Move-Support in diesem Fall bekommt. Ob es tatsächlich notwendig ist, beide explizit (default-Implementierung) zu definieren.

    Nachdem mir jetzt klar gewordern ist, dass der pure virtual Destruktor bereits in der Basisklasse implementiert werden muss, würde mein Entwurf vermutlich so aussehen.

    class Base
    {
    public:
        Base(){}
        virtual ~Base() = 0 {} // evtl. besser im cpp
    
        Base(Base&&) = default;
        Base& operator=(Base&&) = default;
    
        // und der Vollständigkeit halber
        Base(const Base&) = default;
        Base& operator=(const Base&) = default;
    };
    
    class Derived : public Base
    {
    public:
        Derived() {}
    };
    

    Weil Base abstrakt ist und keine Daten-Member hat, kann man aber vielleicht auch einfach auf die default-Implementierungen von move/copy verzichten... In einer Ableitung, die selbst keinen Destruktor definiert/braucht, müsste dann implizit alles bereitgestellt werden und sobald es doch eine konkrete Destruktor-Implementierung geben muss, greift wieder die "Rule of 5" (3, 4 1/2)...?

    class Base
    {
    public:
        virtual ~Base() = 0 {} // evtl. besser im cpp
    };
    
    class Derived : public Base
    {
    public:
    
    protected:
        std::vector<int> m_foo;
    };
    

    Wie hier die Best Practice aussieht bin ich mir aber immer noch nicht so ganz sicher...


Anmelden zum Antworten