(Standard- vs. Kopier-) Konstruktor



  • Klaus82 schrieb:

    Denn dieser dient dazu, Objekten etwas zuzweisen, die bereits initialisiert sind - so besser? 🙂

    Ich würde es anders sagen:

    a = b;
    

    Kopierkonstruktor wird verwendet, wenn Objekt a nicht existiert.
    Assignment operator wird verwendet, wenn Objekt a bereits existiert.



  • out schrieb:

    a = b;
    

    Kopierkonstruktor wird verwendet, wenn Objekt a nicht existiert.
    Assignment operator wird verwendet, wenn Objekt a bereits existiert.

    Aber es geht doch speziell ums Initialisieren, d.h. in der einfachen Zeile von oben

    foo f2 = f
    

    könnt man doch vermuten, dass f2 schon existiert, wenn es f zugewiesen bekommt, d.h. analog zu

    foof f2;
    f2 = f;
    

    sei. Ist es aber nicht. 🙂
    Scheinbar ist der Initailisierungprozess (oder wie man das nennen will) stärker, d.h. die obige Gleichung ist analog zu

    foo f2(f);
    

    Und das ist anscheinend per Konvention einfach so.

    Oder es war eine Designentscheidung oder es ist einfach logisch, das die obige einfache Gleichung zwingend den Kopierkonstruktor erfordert. Aber dazu kenne ich eben C++ noch zu wenig, um im Detail zu verstehen was beim Initialisierungsprozess geschieht.

    Gruß,
    -- Klaus.



  • Okay,

    also dieser Standardkonstruktor hat mir einfach keine Ruhe gelassen. Ganz einfach weil ich zum Thema Vererbung z.B. wieder gelesen habe

    Ist in der Basisklasse der Standardkonstruktor vorhanden, so braucht sich die abgeleitete Klasse nicht um die Initialisierung der geerbten Elemente zu kümmern [..]

    Und ich dachte mir nach der ersten Antwort dieses Posts: Warum sollte ich überhaupt einen Standardkonstruktor definieren, wenn er eh nichts tut und vom Compiler automatisch erzeugt wird.
    Dann hätte eine Basisklasse niemals einen Standarkonstruktor, weil ich mir es immer spare ihn aufzuschreiben!

    Aber der kleine aber feine Unterschied ist die Initialisierungsliste! Ein Standardkonstruktor darf keine Parameter haben und es darf nichts in seinem Rumpf geschehen, aber ich darf über die Initialisierungsliste Variablen (standardmäßig) inititialiseren. D.h. folgendes Beispiel wäre eine Standardkonstruktor

    struct foo
    {
        foo():a(0),b(0){}
        int a;
        int b;
    };
    

    Ein Standarkonstruktor kann also jede Menge tun, nämlich die Variablen initialisieren - aber er darf keine Parameter haben und es darf nichts im Rumpf passieren.

    Jetzt habe ich es doch, oder? 🙂

    Gruß,
    -- Klaus.



  • Doch, es darf etwas im Rumpf geschehen.
    Und definierst du überhaupt keine eigenen Konstruktoren, erstellt der Compiler für dich den Defaultkonstruktor, der tut wirklich ncihts, außer die Defaultkonstruktoren der Member aufzurufen.



  • Klaus82 schrieb:

    Und ich dachte mir nach der ersten Antwort dieses Posts: Warum sollte ich überhaupt einen Standardkonstruktor definieren, wenn er eh nichts tut und vom Compiler automatisch erzeugt wird.

    Weil dein Standardkonstruktor vielleicht was tun will, was der vom Compiler generierte nicht tut.

    Da brauchst du gar nicht mit Initialisiererlisten oder Vererbung ankommen.

    Aber der kleine aber feine Unterschied ist die Initialisierungsliste! Ein Standardkonstruktor darf keine Parameter haben und es darf nichts in seinem Rumpf geschehen, aber ich darf über die Initialisierungsliste Variablen (standardmäßig) inititialiseren.

    Blödsinn, natürlich darf im Rumpf was geschehen. Ein Standardkonstruktor definiert sich -- das hat hier auch schonmal jemand geschrieben -- nur dadurch, dass er ohne Argumente aufgerufen werden kann.

    Eigentlich heißt das Teil auch nicht Standardkonstruktor, sondern Defaultkonstruktor, aber Default ist halt kein deutsches Wort...



  • Okay,
    ich glaube ich verstehe es langsam. Ich habe damit ein wenig herumgespielt und bin bei folgendem etwas verwirrt, wenn bar eine Instanz von foo bekommt:

    #include <iostream>
    
    using namespace std;
    
    struct foo
    {
    	foo();
    	foo(const foo&);
    	int var;
    };
    
    foo::foo():var(5){ cout << "foo defaultconstructor called!" << endl;	}
    
    foo::foo(const foo& other):var(other.var){ cout << "foo copy constructor called!" << endl; }
    
    struct bar
    {
    	bar(foo);
    	foo f;
    	int var;
    };
    
    bar::bar(foo f):f(f){	cout << "bar constructor called" << endl;}
    
    int main()
    {
    	foo f;
    
    	foo f1(f);
    
    	bar b(f);
    
    return 0;
    }
    

    Denn die Ausgabe ist

    foo defaultconstructor called!
    foo copy constructor called!
    foo copy constructor called!
    foo copy constructor called!
    bar constructor called
    

    Zunächst wird in Zeile 27 die Instanz f mittels des Defaultconstructors initialisiert. Dann wird in Zeile 29 eine zweite Instanz f2 durch f erzeugt, das realisiert der Kopierkonstruktor.
    Jetzt verstehe ich aber nicht warum zwei weitere Male der Kopierkonstruktor aufgerufen wird. In Zeile 31 erzeuge ich doch in bar eine Instanz bar.foo durch das Hineinkopieren von f. Also würde ich erwarten, dass nur ein Mal der Kopierkonstruktor aufgerufen wird.
    Oder liegt es daran, dass in der Deklaration des Konstruktors von bar (Zeile 18 bzw. 23) zunächst eine lokale Kopie von foo erzeugt wird? Falls ja, warum wird dazu nicht der Defaultkonstruktor verwendet, sondern auch der Kopierkonstruktor?

    Gruß,
    -- Klaus.



  • Klaus82 schrieb:

    Okay,
    ich glaube ich verstehe es langsam. Ich habe damit ein wenig herumgespielt und bin bei folgendem etwas verwirrt, wenn bar eine Instanz von foo bekommt:

    #include <iostream>
    
    using namespace std;
    
    struct foo
    {
    	foo();
    	foo(const foo&);
    	int var;
    };
    
    foo::foo():var(5){ cout << "foo defaultconstructor called!" << endl;	}
    
    foo::foo(const foo& other):var(other.var){ cout << "foo copy constructor called!" << endl; }
    
    struct bar
    {
    	bar(foo);
    	foo f;
    	int var;
    };
    
    bar::bar(foo f/*foo copy constructor called!*/):f(f/*foo copy constructor called!*/){	cout << "bar constructor called" << endl;}
    
    int main()
    {
    	foo f /*foo defaultconstructor called!*/;
    
    	foo f1(f/*foo copy constructor called!*/);
    
    	bar b(f)/*bar constructor called*/;
    
    return 0;
    }
    


  • Klaus82 schrieb:

    Oder liegt es daran, dass in der Deklaration des Konstruktors von bar (Zeile 18 bzw. 23) zunächst eine lokale Kopie von foo erzeugt wird? Falls ja, warum wird dazu nicht der Defaultkonstruktor verwendet, sondern auch der Kopierkonstruktor

    😕 Das würde doch keinen Sinn machen. Dann wäre der Parameter ja immer ein Defaultobjekt. call-by-value ➡ Kopie erzeugen.



  • out schrieb:

    😕 Das würde doch keinen Sinn machen. Dann wäre der Parameter ja immer ein Defaultobjekt. call-by-value ➡ Kopie erzeugen.

    Mh, jetzt wo du es sagst ...

    Aber ist das nicht wieder ziemlich umständlich? In Zeile 23 erzeugt der Konstruktor von bar zunächst eine Instanz von foo und kopiert den Inhalt dessen hinein was er übergeben bekommt: Kopierkonstruktor.

    Anschließend in der Initialisierungsliste wird die Instanz bar.f erzeugt und bekommt den Inhalt hineinkopiert, den der Parameter f hat: Kopierkonstruktor.

    Eigentlich will dich doch direkt den übergebenen Inhalt in bar.f hineinkopieren?

    Gruß,
    -- Klaus.



  • Klaus82 schrieb:

    Aber ist das nicht wieder ziemlich umständlich? In Zeile 23 erzeugt der Konstruktor von bar zunächst eine Instanz von foo und kopiert den Inhalt dessen hinein was er übergeben bekommt: Kopierkonstruktor.

    Anschließend in der Initialisierungsliste wird die Instanz bar.f erzeugt und bekommt den Inhalt hineinkopiert, den der Parameter f hat: Kopierkonstruktor.

    Eigentlich will dich doch direkt den übergebenen Inhalt in bar.f hineinkopieren?

    Darum nimmt man dafür ja auch call-by-reference. const foo& f



  • out schrieb:

    Darum nimmt man dafür ja auch call-by-reference. const foo& f

    Gut, genau das hatte ich mir auch als Antwort bzw. Grund überlegt! 🙂

    Edit:
    Danke für die ausführliche und geduldige Hilfe. 🙂

    Viele Grüße,
    -- Klaus.


Anmelden zum Antworten