Kopien erzeugen



  • Mal eine Frage,

    wen ich eine Klasse habe, was macht dann der Standardzuweisungsoperator?
    Um mal was ganz einfaches zu schreiben um zu zeigen was ich meine:

    #include <iostream>
    
    using namespace std;
    
    class rect
    {
    private:
            int width;
    
    public:
        rect(int w): width(w){};
        int getW()
        {
            return width;
        }
        rect add(int w)
        {
            rect a(w + this->width);
            return a;
        }
    };
    
    int main()
    {
        rect A(2);
        rect B = A.add(3);
    
        cout << B.getW();
        return 0;
    }
    

    hier wird anscheinend das getan was ich erwarte.
    Was passiert aber wenn ich in einer Klasse Zeiger verwende?
    Oder wo sind Stolperfallen bei diesem Standard?
    Ist es sinnvoll den Operator selbst zu überladen oder nur unnötiger Aufwand?



  • Da fällt mir ein, hier zu gucken:
    http://fara.cs.uni-potsdam.de/~kaufmann/?page=GenCppFaqs&faq=DefCtor2#Answ
    http://fara.cs.uni-potsdam.de/~kaufmann/?page=GenCppFaqs&faq=BigThree#Answ
    Ja, bei Zeigern kracht es, wenn man keine eigenen Versionen dieser Funktionen baut.



  • shisha schrieb:

    [...]
    was macht dann der Standardzuweisungsoperator?
    [...]

    #include <iostream>
    
    using namespace std;
    
    class rect
    {
    private:
        int width;
    
    public:
        rect(int w): width(w){};
        int getW()
        {
            return width;
        }
        rect add(int w)
        {
            rect a(w + this->width);
            return a;
        }
    };
    
    int main()
    {
        rect A(2);
        rect B = A.add(3);
    
        cout << B.getW();
        return 0;
    }
    

    hier wird anscheinend das getan was ich erwarte.

    Nicht ganz. Hier wird nirgenswo ein Zuweisungsoperator aufgerufen. rect B = A.add(3); ist eine "Kopier-Initialisierung" und hat nichts mit dem Zuweisungsoperator zu tun. Zugewiesen wird nur, wenn es das Objekt schon gibt. Hier wird B über den Kopierkonstruktor initialisiert.

    Kopierkonstruktor und Zuweisungsoperator werden automatisch generiert, wenn beides zutrifft:
    - Du hast nichts vergleichbares deklariert (Templates zählen übrigens nicht)
    - Alle Elemente sind kopierbar bzw zuweisbar. (Wenn Du zB Elemente hast, die const qualifiziert sind oder Referenzen sind, kann kein Zuweisungsoperator generiert werden)

    Implizit generierter Kopierungskonstruktor bzw Zuweisungsoperator führt einfach das Kopieren / Zuweisen element-weise durch.

    shisha schrieb:

    Was passiert aber wenn ich in einer Klasse Zeiger verwende?

    Was soll schon passieren? Die Zeiger werden kopiert bzw zugewiesen. Es gibt da keine Sonderbehandlung im Vergleich zu anderen skalaren Typen wie int, double, etc.

    shisha schrieb:

    Oder wo sind Stolperfallen bei diesem Standard?
    Ist es sinnvoll den Operator selbst zu überladen oder nur unnötiger Aufwand?

    Du solltest schon wissen, wann diese Funktionen automatisch generiert werden und was sie genau machen. Wenn die implizit generierten Versionen das richtige tun, macht es keinen Sinn, das selbst zu schreiben.



  • Nicht ganz. Hier wird nirgenswo ein Zuweisungsoperator aufgerufen. rect B = A.add(3); ist eine "Kopier-Initialisierung" und hat nichts mit dem Zuweisungsoperator zu tun. Zugewiesen wird nur, wenn es das Objekt schon gibt. Hier wird B über den Kopierkonstruktor initialisiert.

    Welche Funktion muss ich geanu selbst definieren um das Verhalten selbst zu kontrollieren?

    rect::rect(rect& other)

    ?



  • Jep.

    #edit: Bzw. mach den Parameter lieber const. Geht zwar auch so, beugt aber Programmierfehlern vor.



  • shisha schrieb:

    Nicht ganz. Hier wird nirgenswo ein Zuweisungsoperator aufgerufen.

    Welche Funktion muss ich geanu selbst definieren um das Verhalten selbst zu kontrollieren?

    😮
    Na den Kopier-Konstruktor

    rect::rect(rect& other)

    Das ist zwar laut C++ Standard ein Kopierkonstruktor, hilft Dir aber nicht, weil Du so keine Kopien von konstanten oder temporären rect-Objekten erzeugen kannst.

    rect::rect(rect const& other)
    

    Besorg Dir ein gutes C++ Buch, was diese Basics erklärt. Es lohnt sich. Echt.



  • danke für den tipp,

    bücher sind vorhanden, aber man vergisst ja so einiges^^
    ich komme oft gar nicht drauf dass es da was zu beachten gab 😞



  • hmm dennoch:

    warum klappt das nicht so wie ich es gerne hätte?

    #include <iostream>
    
    using namespace std;
    
    class rect
    {
    private:
            int width;
    
    public:
        rect(int w): width(w){};
        rect(const rect& other)
        {
            width = other.width +10;
        }
        int getW()
        {
            return width;
        }
        rect add(int w)
        {
            rect a(w + this->width);
            return a;
        }
    };
    
    int main()
    {
        rect A(2);
        rect B = A.add(3);
    
        rect C(B);
    
        cout << B.getW() << endl;
        cout << B.getW();
        return 0;
    }
    

    ich bekomme bei B w = 5
    das bedeutet dass mein "kopierkonstruktor" nicht verwendet wird.
    Aber wieso? Das widerspricht dem was ich auf wikipedia gelesen habe und dem was krümelkacker gesagt hat oder?



  • shisha schrieb:

    Aber wieso? Das widerspricht dem was ich auf wikipedia gelesen habe und dem was krümelkacker gesagt hat oder?

    Der krümelkacker wird seinem Namen nicht gerecht. 😃

    Es kommen noch Optimierungen ins Spiel, so dass man sich einen Konstruktoraufruf spart.

    Siehe: http://www.gotw.ca/gotw/036.htm

    Du kannst die Optimierungen bei dir ausschalten, dann erhälst du das gewünschte Ergebnis.



  • shisha schrieb:

    das bedeutet dass mein "kopierkonstruktor" nicht verwendet wird. Aber wieso? Das widerspricht dem was ich auf wikipedia gelesen habe und dem was krümelkacker gesagt hat oder?

    Ja und Nein. Der copy ctor wird garantiert verwendet, wenn ein Objekt kopiert wird. Es ist nur so, dass der Compiler die "Notwendigkeit des Kopierens" in bestimmten Fällen wegoptimieren darf -- auch dann, wenn das, was im copy ctor steht, "Seiteneffekte" hat. Trotzdem muss ein copy ctor aufrufbar sein, damit die Kopierinitialisierung funktioniert. Siehe §12.8/15 im Standard. Dein Compiler hat zwei Optimierungen vorgenommen:
    - copy elision rect::add::a <-> return-wert ("NRVO" = named return value optimization)
    - copy elision return-wert <-> B (erlaubt, da rechte Seite ein "rvalue" ist)

    Das muss man gar nicht so genau wissen, ist aber praktisch. Siehe auch "Want Speed? Pass by Value".



  • das ganze ist sehr interessant und ich werd mir mal anschauen im moment möchte ich aber nur dass das geschieht was ich gerne hätte ohne dass ich dem compiler die möglichkeit der optimierung nehmen möchte,

    gibt es eine einfache möglichkeit?



  • shisha schrieb:

    im moment möchte ich aber nur dass das geschieht was ich gerne hätte ohne dass ich dem compiler die möglichkeit der optimierung nehmen möchte,

    Kannst Du das genauer beschreiben? Warum glaubst Du, dass hier ein eigener copy ctor bzw operator= nicht ausreichend ist?



  • shisha schrieb:

    ... im moment möchte ich aber nur dass das geschieht was ich gerne hätte ohne dass ich dem compiler die möglichkeit der optimierung nehmen möchte...

    Ähm, nur damit das klar wird. Mein Vorschlag die Optimierungen auszuschalten war nur als Hilfe gedacht das Problem zu erfassen und nicht als ernsthafte Lösung damit dein Code das tut was du willst.



  • Heißt das dass die Überladung des = Opearators alle Probleme aus dem Weg schaffen würde?

    Also wird dann doch eine Zuweisung durchgeführt?

    Und noch etwas,

    beim überladen:

    was ist der unterschied zwischen

    rect& rect::operator=(const rect &other)
    rect& rect::operator=(rect &other)
    

    Und wieso gebe ich hier eine Referenz zurück wenn bei + zB keine übergeben wird
    also ich lese grad in einem Tutorial nach...

    tBruch tBruch::operator+(long summand)
    tAuto &tAuto::operator=(const tAuto &k)
    

    Die Erklärung :

    Sie sehen im Beispiel, dass die Rückgabe über eine Referenz erfolgt. Das ist erforderlich, damit nicht noch einmal zusätzlich der Kopierkonstruktor aufgerufen wird.

    Das ganze kommt mir plötzlich so furchtbar kompliziert vor...



  • shisha schrieb:

    Heißt das dass die Überladung des = Opearators alle Probleme aus dem Weg schaffen würde?

    Welche Probleme? Und wie kommst Du darauf?

    shisha schrieb:

    Also wird dann doch eine Zuweisung durchgeführt?

    In Deinem ersten Beispiel nicht, nein.

    shisha schrieb:

    Und noch etwas,
    beim überladen:
    was ist der unterschied zwischen

    rect& rect::operator=(const rect &other)
    rect& rect::operator=(rect &other)
    

    operator= ist hier nichts besonderes. Das ist der gleiche Unterschied, den es zwischen

    void foo(int      & );
    void foo(int const& );
    

    gibt.

    shisha schrieb:

    [...] ich lese grad in einem Tutorial nach [...]

    Newsflash: 99% der C++ Tutorials sind Müll.



  • shisha schrieb:

    was ist der unterschied zwischen

    rect& rect::operator=(const rect &other)
    rect& rect::operator=(rect &other)
    

    Und wieso gebe ich hier eine Referenz zurück wenn bei + zB keine übergeben wird
    also ich lese grad in einem Tutorial nach...

    tBruch tBruch::operator+(long summand)
    tAuto &tAuto::operator=(const tAuto &k)
    

    Die Erklärung :

    Sie sehen im Beispiel, dass die Rückgabe über eine Referenz erfolgt. Das ist erforderlich, damit nicht noch einmal zusätzlich der Kopierkonstruktor aufgerufen wird.

    Die Erklärung ist Müll.

    Der Unterschied ist recht einfach, beim + operator gibst du das Ergebnis zurück, beim = operator eine Referenz auf sich selbst damit beispielsweise so etwas möglich wird:

    MyClass a, b, c;
    a = b = c;
    
    class MyClass
    {
    public:
        MyClass operator+(const MyClass &other)
        {
            ...
            return ret;
        }
    
        MyClass &operator=(const MyClass &other)
        {
            ...
            return *this;
        }
    };
    

    Du musst beim = operator letztlich auch nichts zurückgeben wenn du nicht willst!
    Genauso wie beim + operator! Es wird dann halt ziemlich verwirrend zum benutzen ;).



  • blub² schrieb:

    Der Unterschied ist recht einfach, beim + operator gibst du das Ergebnis zurück, beim = operator eine Referenz auf sich selbst damit beispielsweise so etwas möglich wird:

    MyClass a, b, c;
    a = b = c;
    

    Das geht auch unabhängig davon, ob operator= eine Referenz oder einen Wert liefert. Die goldene Regel heißt "do as the ints do" und eine Zuweisung i=bla , wobei i eine int-Variable ist, liefert nunmal einen Lvalue-Ausdruck auf das Objekt i selbst. So ist es in C und so ist es in C++.

    @shisha: Bzgl Operator Overloading solltest Du in den Artikel zum Thema schauen. Vor allen Dingen solltest Du Dir mal endlich 1-2 vernünftige C++ Bücher besorgen.



  • ok bringen wir das ganze vlt doch mal zum abschluss.

    #include <iostream>
    
    using namespace std;
    
    class rect
    {
    private:
            int width;
    
    public:
        rect(int w): width(w){};
        rect(const rect& other)
        {
            width = 10;//other.width +1;
        }
        int getW()
        {
            return width;
        }
        rect add(int w)
        {
            rect a(w + this->width);
            return a;
        }
    
        rect& operator=(const rect& other)
        {
            this->width = other.width;
            return *this;
        }
    };
    
    int main()
    {
        rect A(2);
        rect B = A.add(3) ;
    
        rect C(B);
        rect D(0);
    
        D = C;
    
        cout << B.getW() << endl;
        cout << C.getW() << endl;
        cout << D.getW();
        return 0;
    }
    

    Ist das halbwegs annehmbar geschrieben?

    Zum Buch:
    Ich hab hier einige C++ Bücher
    C++ Das Einsteigerseminar - war mein erstes und ein absoluter Fehlkauf
    dann Visual C++ 2008 von Louis (das leider viel zu viel CLI macht)

    Die Standardwerke finde ich schon im Einstieg sehr schwierig (vor allem den Primer, den ich leider nicht hier habe).

    aber ich gleube ohnehin dass ich learning by doing anstreben sollte. Nur graue Theorie finde ich immer verwirrend 🙂

    Aber danke dass ihr mir soviel helft... auch wenn mein Schädel jetzt brummt und ich keinen Gedanken mehr heute verschwenden werde ^^



  • shisha schrieb:

    ...
    

    Ist das halbwegs annehmbar geschrieben?

    Definiere "annehmbar". Dein copy ctor macht wenig Sinn. Der eigene Zuweisungsoperator ist überflüssig, weil der Compiler das ganz genauso machen würde. Das Semikolon hinter dem } in der Zeile, in der Du den ersten Konstruktor deklarierst und definierst, ist auch überflüssig.

    shisha schrieb:

    aber ich gleube ohnehin dass ich learning by doing anstreben sollte. Nur graue Theorie finde ich immer verwirrend 🙂

    Learning by doing schließt nicht aus, dass man sich Basiswissen anliest.



  • krümelkacker schrieb:

    blub² schrieb:

    Der Unterschied ist recht einfach, beim + operator gibst du das Ergebnis zurück, beim = operator eine Referenz auf sich selbst damit beispielsweise so etwas möglich wird:

    MyClass a, b, c;
    a = b = c;
    

    Das geht auch unabhängig davon, ob operator= eine Referenz oder einen Wert liefert. Die goldene Regel heißt "do as the ints do" und eine Zuweisung i=bla , wobei i eine int-Variable ist, liefert nunmal einen Lvalue-Ausdruck auf das Objekt i selbst. So ist es in C und so ist es in C++.

    @shisha: Bzgl Operator Overloading solltest Du in den Artikel zum Thema schauen. Vor allen Dingen solltest Du Dir mal endlich 1-2 vernünftige C++ Bücher besorgen.

    Ich schreibe heute irgendwie nur Mist, ich war jetzt ganz in Gedanken bei Streams und überladenen >> bzw. << Operatoren...


Log in to reply