Speicherverwaltung von std::string und Konsorten
-
Hallo,
Weiß jemand von euch, wie std::string und die ganzen Container aus der STL das mit dem Speicher handhaben? Die müssen den ja dynamisch anfordern. Ich vermute mal das geht intern über 'new'? Geben die den Speicher dann auch wieder automatisch frei, sobald er nichtmehr benötigt wird, oder muss sich der Programmierer selbst darum kümmern, indem er dann 'clear' oder sonstige Member-Funktionen aufruft?
Bisher habe ich da nämlich nichts unternommen, um den Speicher wieder freizugeben. Habe aber gerade ein etwas größeres, privates Projekt am Laufen und es wäre schade, wenn es deshalb zu Speicher-Lecks kommen könnte. Würde mich über Antworten freuen
-
Sie benutzen einen Allokator zur Allokation, Deallokation und Erzeugung der Elemente. Die kann man auch austauschen.
Zu einem allocate wird natürlich immer automatisch ein entsprechendes deallocate aufgerufen, zu jedem create ein destroy, ohne dass man da aufpassen muss.
-
Cpp_Confused schrieb:
Weiß jemand von euch, wie std::string und die ganzen Container aus der STL das mit dem Speicher handhaben? Die müssen den ja dynamisch anfordern. Ich vermute mal das geht intern über 'new'? Geben die den Speicher dann auch wieder automatisch frei, sobald er nicht mehr benötigt wird, oder muss sich der Programmierer selbst darum kümmern, indem er dann 'clear' oder sonstige Member-Funktionen aufruft?
Ja klar; das ist ja der Zweck des Ganzen. Der Speicher wird sauber spätestens im Destruktor von string&Co wieder aufgeräumt. Ähnliches gilt auch für fstream - im Destruktor von fstream wird die Datei immer geschlossen, falls sie vorher auf war.
Folgender Code
{ std::vector< int > arr; arr.clear(); // vector initialisieren // viel Code arr.clear(); // sicher ist sicher }
entlarvt den Anfänger. Das clear() ist in beiden Fällen völlig überflüssig. Der Kommentar ist ein OriginalkommentarGoogle mal nach RAII
-
Vor allem gibt clear nichtmal Speicher frei, sondern setzt nur den Putzeiger und den Objectcount auf 0.
Die einzige Methode den Speicher wirklich manuell freizugeben erfordert auch dass der Destruktor den Speicher freigibt.
-
Das tolle an C++ ist, dass Du bei benutzerdefinierten Typen selbst definieren kannst, was passieren soll, wenn ein Objekt dieses Typs initialisiert, kopiert und zerstört werden soll. Für bestimmte Typen wie zB std::string und std::vector ist das sehr sinvoll. Man kann sie als "Black-Box" behandeln. std::string ist etwas, was sich ähnlich wie int oder double verhält bzgl Kopieren und Zuweisen. Man kann Variaben davon anlegen und diese bestimmte Werte speichern lassen. Im Falle von std::string sind das eben Zeichenketten statt Ganzzahlen oder Fleißkommazahlen. Intern wird dafür irgendwie dynamisch Speicher reserviert, ja. Das kann einem aber eigentlich egal sein, wie das genau funktioniert. Benutzen kann man diesen Typ trotzdem.
Wenn Du eine Klasse eine Resource (wie zB dynamisch allozierter Speicher) verwalten lassen möchtest, brauchst Du die "großen Drei":
class string_uebung { public: ... /// steuert, wie man string-objekte kopiert string_uebung(string_uebung const& x); /// steuert, was bei Zuweisungen passieren soll string_uebung& operator=(string_uebung const& x); /// steuert, was passieren soll, wenn das Objekt zerstört wird ~string_uebung(); ... };
Definiert man diese Operationen nicht selbst, werden sie vom Compiler generiert, der einfach elementweise kopiert/zuweist und nichts besonderes im Destruktor macht. Das sollte eigentlich auch der Regelfall sein. Wenn Du es für nötig hältst, bei einem großteil Deiner Klassen diese Funktionen selbst zu definieren, machst Du etwas falsch; denn Du kannst "größere" Klassen aus einfacheren, kleinen zusammensetzen, die jeweils etwas von sich aus Verwalten können (wie auch immer).
schlecht:
struct person { char* vorname; char* nachname; person(const char* v, const char* n) : vorname(strdup(v)) , nachname(strdup(n)) ~person() { free(vorname); free(nachname); } // Oops! Der Programmierer hat auch Kopierkonstruktor // und Zuweisungsoperator vergessen!!! // So kopiert der Compiler nur die rohen Zeiger alleine! };
gut:
struct person { string vorname; string nachname; };
Wenn hier ein person-Objekt kopiert wird, dann wird elementweise kopiert. In diesem Fall ist das aber okay; denn string ist kein dummer Zeiger. Der Kopierkonstruktor der string-Klasse wird aufgerufen und der macht schon das richtige. Du solltest also, wenn Du die großen Drei selbst definierst, dich höchstens nur um eine Resource manuell kümmern in der Klasse. Alles andere kannst Du aus "schlauen" Objekten zusammensetzen, sodass Du die großen Drei so gut wie nie selbst definieren musst.