Speicherfreigabe von Klasse und ihren Membern.



  • Hallo Zusammen,

    ich habe nur zwei Verständnis-fragen zur Speicherfreigabe von Klasse und ihren Membern.

    1.) Wenn ich eine Klasse schreibe die als Member einige Pointer auf Objekt hat. Werden die Objekte nach dem löschen meiner Klasse auf implizit gelöscht oder muss ich diese explizit im Dekonstruktor mit delete machen?

    2.) Wie schaut der gleiche Fall wie (1) aus mit einem Member vom Type std::vector<Irgendwas*> myVectorMember

    Würde mich über eine kurz oder gerne auch Ausführliche Antwort freuen.

    Grüße, free



  • wenn du nur einen Zeiger auf der Objekt hast, wird er gelöscht wenn die Klasse den Destructor aufruft. Anderst ist es bei new !!!

    Solltest du mit dynamischen Speicher arbeiten, musst du ihn selbst wieder frei geben(mit delete).



  • class Foo
    {
    	int * a;
    	int * b;
    	int c;
    	std::vector<int> vec1;
    	std::vector<int*> vec2;
    
    	Foo()
    	: a(nullptr), b(new int(5)), c(10),
    	  vec1(100, -1), vec2(100, new int(100)) 
    	{ }
    
    	~Foo()
    	{
    		// a muss nicht gelöscht werden, da es ja nie initialisert wurde, in diesem konkreten fall
    		delete b; // da für b per new speicher allokiert wurde
    		// c muss nicht angefasst werden, da automatische destruktion
    
    		// vec1 kümmert sich um alles selbst
    
    		// vec2 kümmert sich um die internen zeiger selbst, wie 
    		// vec1, aber nicht um das was hitner den zeigern steht
    		// daher:
    		for(auto it = vec2.begin(); it != vec2.end(); ++it)
    			delete *it;
    		// oder
    		for(size_t i = 0; i < vec2.size(); ++i)
    			delete vec2[i];
    	}
    };
    

    hoffe, das wird so klarer.
    du musst dich um rohe zeiger selbst kümmern, um jegliche verwaltungen. du kannst das zwar an automatische funktionen wie kon- oder destruktoren abgeben, aber dort musst du es dann auch schreiben. besser sind smart pointer...



  • free schrieb:

    1.) Wenn ich eine Klasse schreibe die als Member einige Pointer auf Objekt hat. Werden die Objekte nach dem löschen meiner Klasse auf implizit gelöscht oder muss ich diese explizit im Dekonstruktor mit delete machen?

    Ja, werden sie mit unique_ptr . In C++ gibt man so gut wie nie explizit Speicher frei. Das erledigen stets RAII-Helfer wie vector , shared_ptr oder eben unique_ptr .

    free schrieb:

    2.) Wie schaut der gleiche Fall wie (1) aus mit einem Member vom Type std::vector<Irgendwas*> myVectorMember

    Du meinst vector<unique_ptr<Irgendwas>> ? Da muss man im Gegensatz zum rohen Zeiger nur beachten, dass unique_ptr movable, aber nicht kopierbar ist. Das Stichwort lautet Rvalue-Referenz.

    Skym0sh0 schrieb:

    class Foo
    {
    	int * a;
    	int * b;
    	int c;
    	std::vector<int> vec1;
    	std::vector<int*> vec2;
    	
    	Foo()
    	: a(nullptr), b(new int(5)), c(10),
    	  vec1(100, -1), vec2(100, new int(100)) 
    	{ }
    	
    	~Foo()
    	{
    		// a muss nicht gelöscht werden, da es ja nie initialisert wurde, in diesem konkreten fall
    		delete b; // da für b per new speicher allokiert wurde
    		// c muss nicht angefasst werden, da automatische destruktion
    		
    		// vec1 kümmert sich um alles selbst
    		
    		// vec2 kümmert sich um die internen zeiger selbst, wie 
    		// vec1, aber nicht um das was hitner den zeigern steht
    		// daher:
    		for(auto it = vec2.begin(); it != vec2.end(); ++it)
    			delete *it;
    		// oder
    		for(size_t i = 0; i < vec2.size(); ++i)
    			delete vec2[i];
    	}
    };
    

    Der Konstruktor ist fehlerhaft, weil er nicht ausnahmesicher ist.
    vec2(100, new int(100)) ist Blödsinn und erzeugt undefiniertes Verhalten im Destruktor.

    In (richtigem) C++ sähe das Beispiel so aus:

    class Foo
    {
    	std::unique_ptr<int> a;
    	std::unique_ptr<int> b;
    	int c;
    	std::vector<int> vec1;
    	std::vector<std::unique_ptr<int>> vec2;
    
    	Foo()
    	: b(new int(5)), c(10),
    	  vec1(100, -1), vec2(100) 
    	{
    		//Falsch: vec2(100, new int(100)) 
    		//Richtig:
    		std::generate(begin(vec2), end(vec2), []()
    		{
    			return std::unique_ptr<int>(new int(0));
    		});
    	}
    }
    

    Jawohl, kein Destruktor. Den implementiert man in C++ fast nie.


  • Mod

    Man sollte noch dazu sagen, dass ein Zeigermember auf ein einzelnes, nicht-polymorphes Objekt(hier int), welches im Konstruktor mit new von der Klasse selber erzeugt wird, ebenfalls Unsinn ist und nur in Beispiele, niemals in echten Code, gehört.



  • dafür ists ja ein beispiel 🙂

    TyRoXx schrieb:

    Der Konstruktor ist fehlerhaft, weil er nicht ausnahmesicher ist.

    naja ausnahmesicherheiten gehen ja etwas weiter schon, es ging ja erstmal nur ums prinzip von zeigern und deren zuständigkeiten

    TyRoXx schrieb:

    vec2(100, new int(100)) ist Blödsinn und erzeugt undefiniertes Verhalten im Destruktor.

    ok, jetzt muss ich mal überlegen: du meinst, weil der returnwert von new an den construktor von vector übergeben wird, und der diesen einfach stupide weiter in jedes element (also in jeden gespeicherten zeiger) schiebt?

    halt stop, ich sehe gerade der vector erwartet dort eine referenz. das heisst, er arbeitet mit einer referenz auf die temporäre variable (die ja der rückgabe wert von new ist) und schiebt diese in seine elemente rein. ja das hört sich schon schräg an...

    kommt das so etwa hin?


  • Mod

    Skym0sh0 schrieb:

    dafür ists ja ein beispiel 🙂

    TyRoXx schrieb:

    Der Konstruktor ist fehlerhaft, weil er nicht ausnahmesicher ist.

    naja ausnahmesicherheiten gehen ja etwas weiter schon, es ging ja erstmal nur ums prinzip von zeigern und deren zuständigkeiten

    Das ist aber ein wichtiger, unabtrennbarer Teil des Themas und man sollte es gleich richtig machen. Je mehr du es dir falsch angewöhnst, desto schwieriger wirst du diese Angewohnheit später wieder los.

    TyRoXx schrieb:

    vec2(100, new int(100)) ist Blödsinn und erzeugt undefiniertes Verhalten im Destruktor.

    ok, jetzt muss ich mal überlegen: du meinst, weil der returnwert von new an den construktor von vector übergeben wird, und der diesen einfach stupide weiter in jedes element (also in jeden gespeicherten zeiger) schiebt?

    halt stop, ich sehe gerade der vector erwartet dort eine referenz. das heisst, er arbeitet mit einer referenz auf die temporäre variable (die ja der rückgabe wert von new ist) und schiebt diese in seine elemente rein. ja das hört sich schon schräg an...

    kommt das so etwa hin?

    Nein. Was hier passiert ist, dass nicht 100 neue ints mit Wert 100 erzeugt werden, sondern es wird ein int mit Wert 100 erzeugt, und dann 100 Kopien des Zeigers darauf gespeichert.



  • ja genau das meine ich. das ist ja der rückgabewert von new


  • Mod

    Skym0sh0 schrieb:

    ja genau das meine ich. das ist ja der rückgabewert von new

    In deinem ersten Satz. Jedoch:

    halt stop, ich sehe gerade der vector erwartet dort eine referenz. das heisst, er arbeitet mit einer referenz auf die temporäre variable (die ja der rückgabe wert von new ist) und schiebt diese in seine elemente rein. ja das hört sich schon schräg an...

    Das ist überhaupt nicht schräg, sondern der Normalfall.



  • das mit der referenz? ja das schon.

    aber eine referenz auf einen zeiger der auch noch temporär ist?
    das hört sich in meinen augen schon komisch an...


  • Mod

    Skym0sh0 schrieb:

    aber eine referenz auf einen zeiger der auch noch temporär ist?
    das hört sich in meinen augen schon komisch an...

    😕 Wieso?

    void foo(const std::string &str)
    {
     std::cout << str;
    }
    
    int main()
    {
     foo("Hello World!\n");
    }
    

    Referenz auf temporäres Objekt. So normal, du hast es vermutlich noch nie bemerkt. Ob das temporäre Objekt nun ein Zeiger ist oder hier ein String (der im Prinzip auch nur ein verkappter Zeiger auf char ist), ist doch egal. Zeiger sind auch Objekte wie alle anderen auch.

    Du darfst auch gerne Kopien von dem Objekt machen, im Prinzip macht der Code hier ja schon eine Kopie auf dem Bildschirm.



  • ok, du hast mich überzeugt


  • Mod

    jetzt wo ich darüber nachdenke:

    vector<string> vec;
    vec.push_back("foo");
    

    Hier muss innerhalb von push_back auf jeden Fall nochmal kopiert (statt gemoved) werden, da dem const T&-Parameter ja nicht zu entnehmen ist, of da ein temporäres Objekt gebunden wurde. Hier besteht noch Korrekturpotential.
    Man bräucht vermutlich noch eine weitere Überladung

    template <typename U>
    void push_back(U&& x) { push_back(implicit_cast<T>(std::forward<U>(x))); }
    

    Das Problem bestand im ursprünglichen rvalue-Proposal nicht, als rvalue-Referenzen noch temporäre Objekte erzeugen konnten. Man kann nat. auch auf emplace_back ausweichen, das scheint mir aber in diesem Fall eine unnatürliche Krücke zu sein.


Anmelden zum Antworten