C++: Objekte als Rückgabewert --> löschen?



  • Hallo zusammen!

    Ich lasse eine Funktion Objekte einer Klasse B zurückgeben.

    #include "B.h"
    class A
    {
     B func() 
        {
             B bObj(...);
             return bObj;
        }
    };
    

    Eine Funktion der Klasse C soll diese Objekte jetzt in einer Endlosschleife nutzen:

    #include "B.h"
    class C
    {
    A a;
    void useBobj() 
        {
           for(;;)
            cout << a.func().get_variable() << endl;  // z.B.
        }
    };
    

    Meinem Verständnis nach werden die Objekte von B auf dem Stack angelegt und zerstört, wenn ihr Scope verlassen wird - das wäre bei mir der Fall, wenn useBobj() verlassen wird, richtig?

    Nun meine Frage: Da später etwa alle 100ms ein solches Objekt erzeugt werden soll (durch Anfragen eine Clients), und in useBobj() quasi die ganze Intelligenz des Programms steckt - also nur bei Programmende verlassen wird, fürchte ich, dass ich dann jede Menge alte Objekte rumfliegen habe. Ist es also nötig / sinnvoll / richtig, für diese nach Gebrauch den Destruktor aufzurufen?

    BPointer = &a.func();
    // do stuff
    BPointer ->~B();
    

    Oder muss ich mir um die Performance keine Gedanken machen?

    Vielen Dank für Eure Antworten!
    lg
    moon

    Edit:
    Ich habe gerade in den Destruktor von B ein

    B::~B()
    {
    cout << "Destruktor von B" << endl;
    }
    

    reingeschrieben.
    Selbst bei der Variante ohne Pointer wird der Text 2x ausgegeben...kann mir das jemand erklären, bitte? 🙂



  • Destruktor manuell aufrufen ist den den allermeisten Fällen keine gute Idee. Ansonsten verstehe ich die Frage nicht so richtig. Insbesondere dieses Beispiel:

    BPointer = &a.func();
    // do stuff
    BPointer ->~B();
    

    Sieht überhaupt nicht danach aus, als wüsstest du was du da tust. Zeig mal ein kompilierbares Minimalbeispiel.



  • Hallo cooky,

    der Pointer sollte einfach auf das neue Objekt zeigen, und dann den Destruktor aufrufen.

    kompilierbares Minibeispiel (ich musste wg. MS VS Klasse C mit D umbenennen):

    // A.h
    #pragma once
    #include "B.h"
    
    class A
    {
    public:
    	B createBobj()
    	{
    		B bObj(123);
    		return bObj;
    	}
    	A(void);
    	~A(void);
    };
    
    // B.h
    #pragma once
    #include <iostream>
    
    class B
    {
    public:
    	int iZahl;
    	B(int i) {iZahl = i;}
    	~B(void)
    	{
    		std::cout << "Destruktor von B aufgerufen" << std::endl;
    	}
    };
    
    // C.h
    #pragma once
    #include "A.h"
    #include <iostream>
    
    class D
    {
    	A a;
    public:
    	void useB_Obj()
    	{
    		for(;;)
    			std::cout << a.createBobj().iZahl << std::endl;
    
    	}
    	D(void);
    	~D(void);
    };
    
    // main.cpp
    #include "D.h"
    #include <iostream>
    
    int main()
    {
    
    	D d;
    	d.useB_Obj();
    	system ("PAUSE");
    	return 0;
    }
    

    Ausgabe:

    123
    Destruktor von B aufgerufen
    Destruktor von B aufgerufen
    123
    Destruktor von B aufgerufen
    Destruktor von B aufgerufen
    ...
    


  • moon12 schrieb:

    Meinem Verständnis nach werden die Objekte von B auf dem Stack angelegt und zerstört, wenn ihr Scope verlassen wird

    richtig

    - das wäre bei mir der Fall, wenn useBobj() verlassen wird, richtig?

    Falsch
    Nach jdem Schleifendurchlauf wird der Scope verlassen und das Objekt zerstört. Ausserdem kann je nach Optimierung noch beim verlassen der Funktion createBObject ein B zerstört werden.



  • Das heißt ich kann meinen Code so benutzen?
    Ich wusste nicht, dass ein Schleifendurchlauf auch als Scope zählt, dann wäre es ja genau das, was ich brauche:
    neues Objekt zurückliefern, damit arbeiten, objekt zerstören bei neuem Schleifendurchlauf

    edit: selber schuld, eine for-schleife hat ja {} Klammern -.-
    also spricht nichts gegen meinen Code...wenn der 1. Destruktoraufruf durch die Optimierung keinen Einfluss hat?
    und wenn ich das Objekt noch einem Pointer übergebe, macht es denk ich mal sinn, vor Schleifenende ein

    pointerAufObj = NULL;
    

    hinzuzufügen, damit der dann nicht auf das zerstörte Objekt zeigt beim nächsten Schleifenstart ?!



  • moon12 schrieb:

    hinzuzufügen, damit der dann nicht auf das zerstörte Objekt zeigt beim nächsten Schleifenstart ?!

    Ich sehe gar keinen Pointer in deinem Beispiel. 😕



  • da ist ja auch keiner 🙂
    ich meinte, wenn ich später noch einen dazupacke, etwa so:

    // C.h
    #pragma once
    #include "A.h"
    #include <iostream>
    
    class D
    {
        A a;
        B *Pointer;
    public:
        void useB_Obj()
        {
            for(;;)
              {
    [b]
                Pointer = &a.createBobj();
                std::cout << Pointer->iZahl << std::endl;
                Pointer = NULL;
    [/b]
              }  
    
        }
        D(void);
        ~D(void);
    };
    

    Is doch ok oder?



  • 1. Warum solltest du das machen? Wozu?
    2. Nein, das meinte ich mit du weist nicht was du tust. Du hast doch selbst erklärt, dass Objekte nur in ihrem Scope leben. In Zeile 17 zeigt der Pointer auf irgendetwas, aber nicht auf ein gültiges Objekt. Das kann funktionieren, muss aber nicht.
    3. Das NULL setzen am Ende ist völlig sinnlos.



  • ok, vergessen wir das mit dem Pointer auf null setzen.
    Aber dann hab ich Dich wohl an anderer Stelle missverstanden. Ich dachte das Objekt wird NACH jedem Schleifendurchlauf zerstört, warum zeigt der Pointer in Zeile 17 dann nicht auf ein gültiges Objekt?

    Nach jdem Schleifendurchlauf wird der Scope verlassen und das Objekt zerstört.



  • Eben sah dein Code ja auch völlig anders aus. Wenn du eine Funktion aufrufst, gibt die dir ein Objekt zurück. Das lebt aber erst mal nur in dem Ausdruck als rvalue. Wenn du es bis dahin nicht "gerettet" hast, ist es verloren. 😉

    {
      int i = foo();
      cout << i; // ok, Wert kopiert
    }
    {
      int* i = &foo();
      cout << *i; // Nicht ok, das worauf i zeigt lebt gar nicht mehr
    }
    

    Aber du hast die eigentlich interessanteste Frage gar nicht beantwortet: Was sollte das bringen?



  • moon12 schrieb:

    Ich dachte das Objekt wird NACH jedem Schleifendurchlauf zerstört, warum zeigt der Pointer in Zeile 17 dann nicht auf ein gültiges Objekt?

    Du weißt deinem Pointer die Adresse eines temporären Objekts zu. Um dir das zu verdeutlichen.

    Zeile 17:

    a.createBobj() liefert ein temporäres B
    Aufruf des Adressoperators auf das temporäre B
    Zuweisung der Adresse auf den Pointer
    Zerstörung des temporären B

    Zeile 18:
    Zugriff über den Pointer ins zerstörte B -> undefiniertes Verhalten



  • Dem entnehme ich, dass das Objekt nicht nach jedem Schleifendurchlauf, sondern direkt nach Rückgabe zerstört wird.
    D.h. ich darf kein Objekt in einer Funktion erzeugen, sondern muss es z.B. als Member einer Klasse eintragen und dann eine Referenz zurückgeben oder wie kann ich dass dann überhaupt realisieren?



  • moon12 schrieb:

    Dem entnehme ich, dass das Objekt nicht nach jedem Schleifendurchlauf, sondern direkt nach Rückgabe zerstört wird.
    D.h. ich darf kein Objekt in einer Funktion erzeugen, sondern muss es z.B. als Member einer Klasse eintragen und dann eine Referenz zurückgeben oder wie kann ich dass dann überhaupt realisieren?

    Die Annahme ist falsch. Du solltest dich definitiv mal mit den verschiedenen Rückgabetypen befassen und welche stärken und Schwächen sie haben. Du kannst durchaus Temporäre Objekte verwenden, dann musst du sie aber entweder direkt verwenden oder einer lokalen Variable zuweisen, Move Operatoren und Move Konstruktoren sind da sehr hilfreich. Desweiteren könntest du das Objekt auch mit new erzeugen und einen Zeiger zurückliefern. Mit new erzeugte Objekte landen nicht auf dem Stack, allerdings würde sich dort auch ein SmartPointer anbieten, wo wir wieder beim Move wären.


Anmelden zum Antworten