Copy-Assignment-Operator bei dereferenzierten Objekten



  • Hi! Bisher habe ich den Copy-Ctor und Copy-Assignment-Operator nicht implementiert oder private gesetzt, um dem Thema aus dem Weg zu gehen.

    Jetzt habe ich mal angefangen die entsprechenden Members zu implementieren, und teilweise habe ich ein für mich nicht verständliches Verhalten festgestellt:

    // Dummy Code:
    
    class base
    {
      int dummy;
    public:
       base();
       base(const base &b);
       base& operator=(base &b);
       virtual ~base();
    };
    
    class special : public base
    {
       int dummy2;
    public:
       special();
       special(const special &s);
       special& operator=(special &s);
    };
    

    Nun, kommt folgendes:

    base *b1 = new special;
    base *b2 = new special;
    
    *b1 = *b2; // Zusweisung
    

    Ich hätte jetzt erwartet, das bei der Zuweisung der special::Operator=(special&) aufgerufen wird. Es wird aber der von base aufgerufen. Erster Gedanke: Sollte nicht zur Laufzeit der konkrete Typ erkannt werden?
    Zweiter Gedanke: Der Operator ist nicht virtual, deshalb ist mir schon irgendwie klar, das ich somit kein late binding habe (logisch).
    Aber was soll man da tun?
    Sollte ich den Copy-Assignment-Op von base private setzen, um dem ganzen aus dem Weg zu gehen? Oder ist es in dem Fall richtig, das nur die hälfte kopiert und zugewiesen wird?



  • Wie soll denn das genaue Verhalten sein?
    Aus Typsicht sind es ja Baseobjekte. Dementsprechend wird auch der passende Operator aufgerufen.
    Mal davon abgesehen ist es schlechtes Design, wenn die Basisklasse Funktionalitäten bietet, die die abgeleitete Klasse nicht hat. Denn das bricht die Is-A-Beziehung auf. Wenn special nicht wie base zugewiesen werden kann, dann ist special eben auch kein base Objekt.



  • Das Problem ist tatsächlich, dass der statische Typ für den Zuweisungsoperator gewählt wird, und der ist nunmal base . Das was dabei dann passiert nennt sich slicing.
    Die Sache ist allgemein relativ kompliziert, wie auch in http://icu-project.org/docs/papers/cpp_report/the_assignment_operator_revisited.html dargestellt wird.
    Sell dir vor du hast eine Klasse special2, ebenfalls von base abeleitet.

    class special2 : public base
    {
      /*...*/
    };
    
    base *b1 = new special;
    base *b2 = new special2;
    
    *b1 = *b2; // ÄÄÄHM was soll hier passieren?
    

    Es gibt sehr verschiedene Meinungen, was die Möglichkeiten und unmöglichkeiten einer korrekten Implementierung angeht. Egal wie man es nun genau angeht, sobald man Zuweisungen über Basisklassenzeiger regeln will, kann das oben gezeigte Problem auftauchen, es tauchen in irgendeiner Art Fehler auf, die auf irgendeine Art abgefangen werden müssen.
    Der pragmatische Tip: Versuch keine Zuweisungen an einen dereferenzierten Basisklassenzeiger oder eine Basisklassenreferenz. Polymorphismus und Zuweisungen beißen sich meist, also: Lass es.
    In polymorphem Kontext wird eh viel mit Zeigern und ähnlichem herumhantiert, und meistens werden dann die hinter den Zeigern hängenden Objekte nicht einander zugewiesen sondern komplett ausgetauscht. In deinem Fall hieße das dann wie folgt:

    base *b1 = new special;
    base *b2 = new special;
    
    b1 = b2->clone();
    

    Allerdings ist in dem Code natürlich ein Speicherleck, also entweder die nötigen deletes einbauen oder Smartpointer und ähnliches bemühen.



  • pumuckl! Danke erstmal für die ausführliche Erklärung. Solltest du auch in deinen nächsten Artikel irgendwie mit einbringen.

    Nun, ich bin da also nicht der Einzige, bei dem so viele Fragezeichen aufkamen. Das beruhigt mich. 😃 Ich muß mir das IBM-Dokument heute abend mal genauer durchlesen. Aber so als kurzfristige Lehre, ziehe ich daraus, das ich den Operator einfach private setzen sollte, wenn ich eine Vererbungsstruktur habe (zumal ich nicht wirklich bisher den echten Bedarf nach dem Operator hatte). Mal sehen was ich da in Zukunft endgültig machen werde.



  • also entweder du verzichtest darauf basisklassen-zeiger oder -referenzen für eine klassen zu verwenden.
    oder du verzichtest darauf die klasse kopierbar zu machen.

    funktioniert in der praxis erstaunlich gut (heisst: es gibt kaum fälle, wo man beides bräuchte)



  • Artchi schrieb:

    Solltest du auch in deinen nächsten Artikel irgendwie mit einbringen.

    Ich werd das Thema mal mit auf meine Ideenliste setzen für kommende Operator-Artikel 😉



  • pumuckl schrieb:

    Die Sache ist allgemein relativ kompliziert, wie auch in http://icu-project.org/docs/papers/cpp_report/the_assignment_operator_revisited.html dargestellt wird.

    Was genau ist da kompliziert?

    Das Gebiet ist doch bereits vollstaendig erforscht, oder?
    Oder uebersehe ich irgendwas?



  • Wäre das ne Alternative für dich ? :

    // Dummy Code:
    
    class base
    {
      int dummy;
      virtual base& assign(const base &b);
    public:
       base();
       base(const base &b);
       base& operator=(const base &b) { assign(b); }
       virtual ~base();
    };
    
    class special : public base
    {
       int dummy2;
       virtual special& assign(const base &b) { /* downcast hier sicher! */ }
    public:
       special();
       special(const special &s);
       special& operator=(const special &s) { base::operator=(s); assign(s); }
    };
    


  • KasF schrieb:

    Wäre das ne Alternative für dich ? :

    Nein!



  • Shade Of Mine schrieb:

    Nein!

    Anworte vernünftig oder unterlasse in Zukunft deine Antworten auf meine Beiträge !



  • Shade Of Mine schrieb:

    Was genau ist da kompliziert?

    Zu entscheiden welche Nachteile man in Kauf nehmen will und welche Vorteile/Features man wirklich braucht. Es gibt halt keine "best practice"



  • pumuckl schrieb:

    Shade Of Mine schrieb:

    Was genau ist da kompliziert?

    Zu entscheiden welche Nachteile man in Kauf nehmen will und welche Vorteile/Features man wirklich braucht. Es gibt halt keine "best practice"

    Mhm...
    Wenn man Reference Typen kopieren will braucht man eine clone Semantik und die kann der op= nicht bieten...

    Sprich:

    Polymorphes Verhalten? entweder nicht kopierbar oder clone-like verhalten.

    Value Typen Verhalten? entweder nicht kopierbar oder über op= kopierbar.

    Dann gibt es nur noch die Spezialsituation mit Collections die ja über ein Basisinterface ansprechbar sind und anhand dessen kopiert werden können. Das geht hier, da eine Collection ja keine daten hinzufügt...



  • KasF schrieb:

    Wäre das ne Alternative für dich ? :

    Ganz pragmatisch und funktioniert. Super! 🙂



  • hustbaer schrieb:

    also entweder du verzichtest darauf basisklassen-zeiger oder -referenzen für eine klassen zu verwenden.
    oder du verzichtest darauf die klasse kopierbar zu machen.

    funktioniert in der praxis erstaunlich gut (heisst: es gibt kaum fälle, wo man beides bräuchte)

    Hem, ich habe in meiner Library tatsächlich alles mit dynamisch erzeugten Objekten zu tun, die alle in Smart-Pointern verwaltet werden. Für Objektübergaben in Funktionen und Return-Objekten brauche ich das tatsächlich keine Kopien und Zuweisungen. Deshalb bisher von mir auch nicht implementiert.

    Wäre eine Überlegung wert einfach das ganze private zu setzen, da es nicht gerade wenig Objekte sind, wo ich das implementieren muß.



  • @Artchi: ja, genau. Oder einfach von boost::noncopyable ableiten.

    Im Prinzip ist das was ich geschrieben habe dasselbe wie Shade nochmal später geschrieben hat, nur etwas anders formuliert.

    Auf jeden Fall sollte man entweder einen passenden Copy-Ctor + Assignment-Operator implementieren (wenn dies möglich und sinnvoll ist), oder die Klasse "noncopyable" machen (d.h. Copy-Ctor und Assignment-Operator "private" machen, bzw. von z.B. boost::noncopyable ableiten, was dazu führt dass Copy-Ctor und Assignment-Operator nicht vom Compiler erstellt werden können).


Anmelden zum Antworten