Impliziter Konstruktoraufruf



  • Ich handhabe das meistens so, dass ich die builtin-typen direkt an einen vector anhänge:

    v.push_back(12);
    

    Bei "eigenen Objekten" verwende ich Zeiger:

    v.push_back(&myobj);
    

    Man muss natürlich im Auge behalten, dass der Zeiger nicht gerade auf ein Objekt zeigt, dass im "nächsten Moment" aus dem Scope fällt und zerstört wird.

    Ansonsten verwende ich hin und wieder mal "fire&forget"-Aufrufe:

    log.push_back(new Message("Don't forget to delete this one, after usage!"));
    

    ... aus der Sicht eines newb


  • Mod

    Karatekatze schrieb:

    Braunstein schrieb:

    push_back kopiert die Objekte. Im genannten Fall sollte der Compiler aber so schlau sein und nur den Konstruktor aufrufen.
    [edit]
    Das kannst du auch leicht selbst prüfen, indem du in Konstruktor, Destruktor, CopyCtor und Zuweisungsoperator deiner Klasse/Struct Ausgaben einbaust.
    [edit]

    Aha, ist es also empfehlenswerter mit Containern von Zeigern zu arbeiten?

    nein. Voreilige Optimierung ist die Wurzel allen Übels. Kopierphobie ebenfalls.



  • Karatekatze schrieb:

    Aha, ist es also empfehlenswerter mit Containern von Zeigern zu arbeiten?

    Nein. Das mußt du von Fall zu Fall selbst entscheiden. Zeiger in Container bringen ganz eigene Schwierigkeiten mit sich, wie Gültigkeitsprüfungen, Deallokation etc.
    Dies kann man mit Containern auf Smart_Pointern oder auch boost::Pointer_Container zwar verbessern, allerdings steigt der Aufwand.

    [edit]
    Hier

    vektor.push_back(Typ());
    

    wird mit ziemlicher Sicherheit nicht kopiert, sondern gleich im vector konstruiert.
    Wie ich schon sagte, Probiers aus.
    [edit]


  • Mod

    Verwende Zeiger, wenn damit eine eigene Bedeutung verknüpft ist; als bloße Optimierung ist es in der Regel verfehlt.


  • Mod

    Braunstein schrieb:

    Im genannten Fall sollte der Compiler aber so schlau sein und nur den Konstruktor aufrufen.

    Im besagten Falle, ist es ihm sogar verboten, das zu tun (wenn der Kopiervorgang beobachtbar ist versteht sich).



  • camper schrieb:

    Braunstein schrieb:

    Im genannten Fall sollte der Compiler aber so schlau sein und nur den Konstruktor aufrufen.

    Im besagten Falle, ist es ihm sogar verboten, das zu tun (wenn der Kopiervorgang beobachtbar ist versteht sich).

    Wie meinst du das?

    Bei Debug jedenfalls ruft er den copy ctor auf.
    Kann er den Kopieraufruf herausoptimieren z.B. wenn ich keinen definiere?



  • Um den Copy-Ctor aufzurufen muss er was zum kopieren haben. In dem genannten Fall müssten also nacheinander Ctor, Copy-Ctor, Dtor aufgerufen werden.

    Wenn Du keinen Copy-Ctor definierst, definiert der Compiler einen, der für jedes Element den Copy-Ctor aufruft. Geht das nicht, weil die Klasse oder eines der Elemente einen privaten Copy-Ctor hat, wirst Du einen Compilefehler bekommen.



  • Hmm... ich verstehe immer noch nicht.

    struct Daten
    {
       int a;
       int b;
    };
    
    class Typ
    {
       private:
         int zahl;
         vector<Daten> datenVektor;
    
       public:
         Typ(int z) {zahl=z; datenVektor.push_back(holeDaten(z));}
         ~Typ() {datenVektor.clear();}
    }
    
    Irgendwo::MacheEtwas()
    {
      vector<Typ> typVektor;
      typVektor.push_back(Typ(1));
      typVektor.push_back(Typ(2));
    }
    

    Hier kopiert er korrekt die Objekte in den Vektor, obwohl der Typ ein nicht primitives Datenelement (Vektorcontainer mit Strukturelementen) enthält.
    Wieso funktioniert das, wo doch kein Copy-Ctor für <Typ> definiert ist?



  • Ich meine der standard Copy-Ctor für <Typ> dürfte das doch gar nicht können.
    Oder doch?



  • Legst du keinen Copy-Ctor an erstellt der Kompiler einen. Hat bestimmt schon einer hier gesagt.
    Und wie macht er das? Er ruft für jeden Member den Copy-Ctor auf. Da der Copy-Ctor von std::vector definiert ist und auch das tut, was man von ihm erwartet, reicht in dem Beispiel der vom Kompiler gestellte Kopier-Konstruktor.

    Gruß
    Don06


  • Mod

    Karatekatze schrieb:

    , wo doch kein Copy-Ctor für <Typ> definiert ist?

    Dem ist nicht so. Immer dann, wenn du nicht selbst einen Copy-Ctor deklarierst, macht der Compiler das für dich (deshalb ist es richtig zu sagen, das jede (definierte) Klasse einen Copy-Ctor hat). Die genaue Signatur dieses implizit deklarierten Copyctors hängt dabei von den Kopierkonstruktoren der direkten und virtuellen Basen und den Membern der Klasse ab. Wenn diese alle einen Kopierkonstruktor mit der Signatur
    T(const T&) oder T(const volatile T&) haben [bzw. es sich bei Membern um skalare Typen handelt oder bei Arrays dies (ggf. rekursiv anzuwenden) für die Elemente gilt], so hat der implizit deklarierte Kopierkonstruktor unserer Klasse U die Form
    U(const U&) andernfalls hat er die Form U(U&)
    Dieser implizit deklarierte Kopierkonstruktor ist public und inline.

    Implizit definiert wird dieser implizit deklarierte Kopierkonstruktor bei der ersten Benutzung desselben - inhaltlich ruft dieser die jeweiligen Kopierkonstruktoren seiner Basen und Member auf. Nun kann es passieren, dass auf einen Kopierkonstruktor einer Basis oder eines Members nicht zugegriffen werden kann, weil dieser private oder bei Membern auch protected ist. Dann ist an dieser Stelle (an der unser Kopierkonstruktor definiert werden muss) ein Fehler und der Compiler muss dies diagnostizieren.

    Mit kleinen Abweichung gilt dies alles sinngemäß auch für den Kopieroperator= - dieser wird ebenfalls ggf. implizit deklariert. Weil er gleichzeitig alle geerbten Kopieroperatoren verdeckt, entsteht leicht der Eindruck, diese würden nicht geerbt. Dem ist aber nicht so.



  • OK, Danke für die Erklärungen!

    Was mich aber stutzig macht ist dass der Copy-Ctor der STL-Container auch für Elemente des Typs struct definiert ist.
    Macht er da ein memcopy oder was?


  • Mod

    Karatekatze schrieb:

    OK, Danke für die Erklärungen!

    Was mich aber stutzig macht ist dass der Copy-Ctor der STL-Container auch für Elemente des Typs struct definiert ist.
    Macht er da ein memcopy oder was?

    Er kopiert elementweise - technisch läuft das auf placement new hinaus, weil es ja keinen Weg gibt (außer in Initialisierungslisten), einen Konstruktor explizit aufzurufen. memcpy & Co. produziert erstens meist undefiniertes Verhalten und würde auch oft das Falsche machen. Gerade dort, wo wir den Copy-ctor selbst definieren, machen wir das weil der implizit definierte ctor das Falsche machen würde - und das wäre bei memcpy nicht anders.



  • camper schrieb:

    Braunstein schrieb:

    Im genannten Fall sollte der Compiler aber so schlau sein und nur den Konstruktor aufrufen.

    Im besagten Falle, ist es ihm sogar verboten, das zu tun (wenn der Kopiervorgang beobachtbar ist versteht sich).

    Bist du sicher? Soweit ich weiß dürfen Kopiekonstrktor-Aufrüfe ausdrücklich auch sonst weg optimiert werden. Eine solche Sonderreglung gibt es, ob sie allerdings hier zutrifft weiß ich nicht.

    Der Compiler muss natürlich immer noch prüfen ob der Kopiekonstrktor aufgerufen werden könnte allerdings braucht er ihn anschließend nicht aufzurufen.


  • Mod

    Ben04 schrieb:

    camper schrieb:

    Braunstein schrieb:

    Im genannten Fall sollte der Compiler aber so schlau sein und nur den Konstruktor aufrufen.

    Im besagten Falle, ist es ihm sogar verboten, das zu tun (wenn der Kopiervorgang beobachtbar ist versteht sich).

    Bist du sicher? Soweit ich weiß dürfen Kopiekonstrktor-Aufrüfe ausdrücklich auch sonst weg optimiert werden. Eine solche Sonderreglung gibt es, ob sie allerdings hier zutrifft weiß ich nicht.

    Der Compiler muss natürlich immer noch prüfen ob der Kopiekonstrktor aufgerufen werden könnte allerdings braucht er ihn anschließend nicht aufzurufen.

    es gibt nur zwei Fälle, in denen es erlaubt ist:

    This elision of copy operations is permitted in the following circumstances (which may be combined to eliminate multiple copies):
    — in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object with the same cv-unqualified type as the function return type, the copy
    operation can be omitted by constructing the automatic object directly into the function’s return value
    — when a temporary class object that has not been bound to a reference (12.2) would be copied to a class object with the same cv-unqualified type, the copy operation can be omitted by constructing the temporary object directly into the target of the omitted copy

    Wir haben es nicht mit einem return statement zu tun - also trifft der erste Fall nicht zu.
    Der zweite Fall trifft ebenfalls nicht zu, denn unser Objekt wurde (beim Funktionsaufruf) an eine Referenz gebunden. verbleibt nur noch die as-if Regel...



  • camper schrieb:

    Er kopiert elementweise - technisch läuft das auf placement new hinaus, weil es ja keinen Weg gibt (außer in Initialisierungslisten), einen Konstruktor explizit aufzurufen. memcpy & Co. produziert erstens meist undefiniertes Verhalten und würde auch oft das Falsche machen.

    Nur funktioniert das sogar mit Typen die z.B. Arrays beinhalten.
    Wie das?!


  • Mod

    sqrt(-1) schrieb:

    camper schrieb:

    Er kopiert elementweise - technisch läuft das auf placement new hinaus, weil es ja keinen Weg gibt (außer in Initialisierungslisten), einen Konstruktor explizit aufzurufen. memcpy & Co. produziert erstens meist undefiniertes Verhalten und würde auch oft das Falsche machen.

    Nur funktioniert das sogar mit Typen die z.B. Arrays beinhalten.
    Wie das?!

    Weil für das Kopieren des Memberarrays der Kopierkonstruktor des betreffenden Typen, der das Array enthält zuständig ist. Der vector kriegt davon gar nichts mit. Im Gegensatz dazu kann aber ein Array nicht selbst Element eines vector sein.



  • Wie läuft das dann bei einem Array ab?
    Werden dann also alle Elemente einzeln kopiert?



  • Der Standard sagt: "- wenn das Subobjekt ein Feld ist, wird jedes Element so kopiert, wie es passend zum Elementtyp ist;"

    Also wird scheinbar tatsächlich jedes Element einzeln kopiert.
    Ob der Compiler da optimiert? Naja...



  • sqrt(-1) schrieb:

    Ob der Compiler da optimiert? Naja...

    Also Kopierschleifen mit inline Zuweisungsoperatoren sind eigentlich eine Spezialität moderner Compiler 😉


Anmelden zum Antworten