Lifetime eines zurückgegebenen Objektes



  • Hallo, ich habe gerade dieses Forum "entdeckt" und möchte es auch gleich ausprobieren:

    Folgendes Problem:
    Wenn ein Objekt ohne das Schlüsselwort "new" in C++ erstellt wird, dann landet es so wie ich es bisher verstanden habe, auf dem Stack und hat nur die Lebensdauer bis der aktuelle Scope zu Ende ist.
    Nun habe ich mir gerade Operatoroverloading beigebracht, und festgestellt, dass ein Objekt, das in einer Klasse erstellt wurde, zurückgegeben wurde und unmittelbar danach ( in der main() Methode ) auf Membervariablen zugegriffen wurde.
    Bisher dachte ich, dass dies ein nicht definiertes, nicht vorhergesehenes Verhalten könnte, da das Objekt zu diesem Zeitpunkt ja gar nicht mehr im Stack sein muss, und habe das returnen eines Objekts gemieden.

    Meine bisherige Annahme: Ich müsste Speicher reservieren (malloc, calloc) um das Objekt am Heap dauerhaft zu speichern und um außerhalb des Scopes sicher darauf zugreifen zu können.

    Also: warum funktioniert folgender Code, bzw was genau habe ich falsch verstanden?

    Zeile 11: Objekterstellung
    Zeile 22: Zugriff auf das Objekt.

    #include<iostream> 
    using namespace std; 
    
    class Complex { 
    private: 
    	int real, imag; 
    public: 
    	Complex(int r = 0, int i =0) {real = r; imag = i;} 
    
    	Complex operator + (Complex const &obj) { 
    		Complex res; 
    		res.real = real + obj.real; 
    		res.imag = imag + obj.imag; 
    		return res; 
    	} 
    	void print() { cout << real << " + i" << imag << endl; } 
    }; 
    
    int main() 
    { 
    	Complex c1(10, 5), c2(2, 4); 
    	Complex c3 = c1 + c2;
    	c3.print(); 
    } 
    
    


  • du verlässt den scope doch gar nicht, bzw. in zeile 14 wird eine kopie von res erstellt, falls du darauf hinauswillst.



  • Du darfst keine Adresse eines lokal auf dem Stack erzeugten Objekts zurückgeben und dann verwenden. So wie in deinem Code ist es aber ausdrücklich vorgesehen. Ein selbst geschriebener Typ kann genauso verwendet werden wie ein int.

    @stud123 sagte in Lifetime eines zurückgegebenen Objektes:

    malloc, calloc

    Wenn schon dann new.

    OT: dir ist std::complex bekannt?



  • Vielen dank für die schnellen Antworten.

    @manni66 sagte in Lifetime eines zurückgegebenen Objektes:

    Du darfst keine Adresse eines lokal auf dem Stack erzeugten Objekts zurückgeben und dann verwenden. So wie in deinem Code ist es aber ausdrücklich vorgesehen.

    --> aufgrund deines Vorredners meine ich, es wurde eine Kopie vom Objekt erstellt und keine Adresse zurückgegeben. Darf ich das? Verwirrt mich jetzt ein bisschen. Darf ich so wie es in der Fragestellung ist nicht umsetzen?

    OT: dir ist std::complex bekannt?

    Complex bekannt? -> Nein, da ich erst 1 Woche C++ lerne, das was dahinter steckt kann man sich glaub ich ohne nachzusehen denken 🙂



  • @stud123 sagte in Lifetime eines zurückgegebenen Objektes:

    @manni66 sagte in Lifetime eines zurückgegebenen Objektes:

    Du darfst keine Adresse eines lokal auf dem Stack erzeugten Objekts zurückgeben und dann verwenden. So wie in deinem Code ist es aber ausdrücklich vorgesehen.

    --> aufgrund deines Vorredners meine ich, es wurde eine Kopie vom Objekt erstellt und keine Adresse zurückgegeben. Darf ich das? Verwirrt mich jetzt ein bisschen. Darf ich so wie es in der Fragestellung ist nicht umsetzen?

    Die Frage verstehe ich nicht. Du zitierst doch sogar So wie in deinem Code ist es aber ausdrücklich vorgesehen.



  • @manni66 wenn ich es nicht darf, wieso funktioniert es denn?



  • du gibst keine adresse, sondern eine kopie von res zurück.



  • @Wade1234 sagte in Lifetime eines zurückgegebenen Objektes:

    du gibst keine adresse, sondern eine kopie von res zurück.

    funktionslokale objekte müssen als rückgabewert kopiert werden. rückgabe als referenz oder pointer ist falsch. funktionslokaler speicher ist nicht mehr gültig, wenn die funktion verlassen wurde.



  • Was schreibt ihr hier von Kopien? Es wird nicht bei jeder Rückgabe etwas kopiert, siehe auch https://en.wikipedia.org/wiki/Copy_elision

    Schau auch mal hier https://en.cppreference.com/w/cpp/language/copy_elision nach NRVO.



  • @stud123 sagte in Lifetime eines zurückgegebenen Objektes:

    @manni66 wenn ich es nicht darf, wieso funktioniert es denn?

    Wie kammt man von ausdrücklich vorgesehen auf nicht dürfen? Das ist mit völlig schleierhaft. Du darfst und sollst das.



  • @Wade1234
    Ja schon, aber die Kopie wurde in der Zeile 11 erstellt und "lebt" im Scope bis zu Zeile 15. Danach gibt es die Adresse auf die Kopie zwar nach wie vor, (weil ja returnt), jedoch ist dieser Adressblock (in dem sich Complex res; befindet ) nicht mehr für dieses Objekt reserviert und kann bspw von einer lokalen Variablen oder von einem Objekt neu beschrieben werden?

    --> also sowas wie dangling pointer.

    In Java bspw "lebt" ein Objekt bis keine Referenz auf dieses Objekt mehr existiert und wird dann (evtl.) vom Garbage Collector zerstört. In C++ wird beim verlassen des aktuellen Scopes der Speicherplatz freigegeben, egal ob nun eine Adresse darauf zeigt oder auch nicht.

    Korrigiert mich bitte wenn ich was falsch gelernt habe



  • @stud123 Mit c3 in main gibt es aber einen neuen Speicherplatz.
    Dorthin wird das Object (der Inhalt) beim return kopiert.

    Das ist kein Unterschied zu einem int oder double



  • Danke, das wollte ich hören 🙂
    Lg



  • @stud123 sagte in Lifetime eines zurückgegebenen Objektes:

    Danke, das wollte ich hören 🙂
    Lg

    Gleich die erste Antwort sagt das aus:

    @Wade1234 sagte in Lifetime eines zurückgegebenen Objektes:

    bzw. in zeile 14 wird eine kopie von res erstellt,

    Und in der zweiten Antwort auch nochmal.

    @manni66 sagte in Lifetime eines zurückgegebenen Objektes:

    So wie in deinem Code ist es aber ausdrücklich vorgesehen. Ein selbst geschriebener Typ kann genauso verwendet werden wie ein int.

    Was dachtest du, was c3 ist/macht?



  • Füge mal noch folgenden Kopierkonstruktor zu deiner Klasse hinzu:

    Complex(const Complex &other)
      //: real(other.real), imag(other.imag)
    {
      cout << "copy constructor" << endl;
    }
    

    Durch Einkommentieren der 2. Zeile erhältst du dann auch die korrekte Kopie, aber zum Testen für dich lass es ersteinmal so laufen (so daß du dann ein falsches (zufälliges) Ergebnis erhältst).
    Wenn du im Debugger den Code im Einzelschrittmodus laufen läßt, dann siehst du auch, daß der Kopierkonstruktor erst beim return res aufgerufen wird.



  • @Th69 sagte in Lifetime eines zurückgegebenen Objektes:

    Wenn du im Debugger den Code im Einzelschrittmodus laufen läßt, dann siehst du auch, daß der Kopierkonstruktor erst beim return res aufgerufen wird.

    Oder auch nicht.

    Wie ich schon sagte, wird der normalerweise wegoptimiert. Es wird dann vom operator+ direkt in deine Variable c3 geschrieben! Man muss z.B. den g++ schon mit -fno-elide-constructors zwingen, das nicht zu tun. Sogar mit -O0 wird der sonst in diesem Beispiel wegoptimiert (hier ein g++ 7.4.0).



  • Im Debug-Modus ohne Optimierungen aber nicht (zumindestens im VS mit dem MSVC).



  • selbst wenn: sobald da cout drin steht, wird gar nichts wegoptimiert.



  • @Wade1234 sagte in Lifetime eines zurückgegebenen Objektes:

    selbst wenn: sobald da cout drin steht, wird gar nichts wegoptimiert.

    Die Ausage ist falsch!



  • @manni66 sagte in Lifetime eines zurückgegebenen Objektes:

    @Wade1234 sagte in Lifetime eines zurückgegebenen Objektes:

    selbst wenn: sobald da cout drin steht, wird gar nichts wegoptimiert.

    Die Ausage ist falsch!

    ja aber die programmausgabe, dass da ein kopiervorgang stattfand, muss ja erhalten bleiben. der compiler kann sowas ja nicht einfach entfernen.


Log in to reply