Placement delete, sowie weitere Fragen
-
Servus Nexus,
wenn delete nur den Speicher frei gibt, wer ruft den Destruktor auf?
Entschuldige für diese Fragen, aber mein Buch ist 250km von mir entfernt.
Kein Placement-Delete? Was ist im folgenden Codestück deklariert?
public: void *operator new(unsigned int class_size, My_Pool *pool); void *operator new(unsigned int class_size, My_Pool &pool); void operator delete(void*) { } void operator delete(void*, My_Pool*) { } void operator delete(void*, My_Pool&) { } private: void *operator new(unsigned int) {}
-
Siassei schrieb:
wenn delete nur den Speicher frei gibt, wer ruft den Destruktor auf?
Du musst unterscheiden zwischen
deleteundoperator delete.// operator delete gibt nur Speicher frei - analog zu operator new. void* mem = operator new(231); operator delete(mem); // delete ruft den Destruktor auf und gibt den Speicher frei - analog zu new. T* ptr = new T; delete ptr; // schrittweise kannst du dir delete so vorstellen: ptr->~T(); // Destruktor aufrufen operator delete(ptr); // Speicher freigebenSiassei schrieb:
Kein Placement-Delete? Was ist im folgenden Codestück deklariert?
Überladene
new- unddelete-Operatoren. Wie gesagt, Placement New wird benötigt, um an einer rohen Speicherstelle den Konstruktor eines Objekts aufzurufen.// Statt T* ptr = new T; // kannst du schrittweise folgendes tun: T* ptr = static_cast<T*>(operator new(sizeof(T))); // Speicher anfordern new (ptr) T(); // Objekt dort konstruierenÜbrigens hat der
operator newals ersten Parameterstd::size_t- das muss nicht zwangsläufig das Gleiche sein wieunsigned int. Aber ich frage dich nochmals, wieso willst du eine eigene Speicherverwaltung?
-
Sorry, für die Angaben des ersten Beitrags. Ich meinte natürlich den Destruktor in Verbindung mit delete. Es war schon etwas spät und vielleicht hatte ich auch schon ein paar Maß zu viel. Ein Bierzelt ist was schönes

Aber ich frage dich nochmals, wieso willst du eine eigene Speicherverwaltung?
Nennen wir es jucks und tollerei. Oder eher Interesse an C++, Programmierung, Zeitvertreib, ...

Dass size_t nicht unbedingt unsigned int ist, weiß ich auch. Frage, darf size_t auch ein negatives Vorzeichen tragen oder ist das vom Standard ausgeschlossen? Grund der Frage. Ich hab mir eine Implemtierung angeschaut und da haben die Tatsächlich geprüft ob der Parameter vom Operator new negativ ist.
Als ersten Anhaltspunkt habe ich mir die Source von PJLIB besorgt, woraus auch der Fetzen stammt. Vom Design bin ich nicht gerade begeistert, da diese Implemtierung doch sehr speziell ist und auf Objective-C beruht.
Das Speichern der vergebenen Zeigern + Informationen über die Größe, vieleicht auch Typ (wenn ich herausfinde wie ich das Hinbekomme), soll als Informationsquelle für die spätere Bereinigung und analyse Quelle dienen. Ich weiß, dass das alles schon gibt. Ich möchte mir damit auch nur Wissen aneignen, dass ich sonst niemals bekähme

Ein Frage hätte ich noch. Ich überschreibe den "normalen" Operator delete (global oder für eine Klasse). Muss ich den Destruktor manuell im Code aufrufen oder erledigt sich das beim bereinigen mit z.B. free().
// z.B eigener operator new/delete (global) static MyPool* pool; void* temp = malloc(sizeof(MyPool)); pool = new (temp) MyPool(); void *operator new(std::size_t size) { return pool.malloc(size); } void operator delete(void* mem) { pool.push_back(mem); // Oder // pool.free(mem); } // So Abc *a = new Abc(); a->~Abc(); delete a; // oder Abc *a = new Abc(); delete a; // Wird der Destruktor hier automatisch // aufgerufen oder nicht?Falls ich den Destruktor manuell Aurfufen muss, sehe ich da nur den Weg einer Basisklasse. Von dieser müssten alle verwaltbare Klassen abgeleitet werden. Das kommt mir doch bekannt vor
und gefällt mir nicht. Kann ich irgendwie die Typinformationen speichern, damit ich folgendes machen kannmap<void*, myTypeInfoStruct> &info; void* p; //.. map<void*, myTypeInfoStruct>::iterator it = info.find(p); if(it != info.end()) { myTypeInfoStruct i = it->second; (dynamic_cast<i.zeigerAufKlasse()> (p))->i.adresseDesDestruktors(); } free(p)Wie sieht es eigentlich bei Stukturen aus? Wenn ich mich nicht recht erinnere, gibt es zwei Sorten. C konforme und nicht C konforme Strukturen. Ersteres müsste ich doch mich free() einfach freigeben können, oder?
Mit was könnte ich das Überprüfen? In <type_traits> konnte ich nichts finden.
Fragen, über Fragen und ich finde keine Anworten

-
Wenn es nur zum Herumexperimentieren ist, ist es ganz okay. Nur würde ich aufpassen, bevor ich sowas produktiv einsetze...

Siassei schrieb:
Dass size_t nicht unbedingt unsigned int ist, weiß ich auch. Frage, darf size_t auch ein negatives Vorzeichen tragen oder ist das vom Standard ausgeschlossen? Grund der Frage. Ich hab mir eine Implemtierung angeschaut und da haben die Tatsächlich geprüft ob der Parameter vom Operator new negativ ist.
std::size_tist einunsigned-Typ, muss also positives Vorzeichen haben. Eine Prüfung auf negative Werte ist Schwachsinn, oder sogar gefährlich, da diese implizit zuunsigendgecastet werden.
Betrachte folgendes Beispiel:std::size_t size; if (size <= -1) throw bla; // oh oh...Siassei schrieb:
Ein Frage hätte ich noch. Ich überschreibe den "normalen" Operator delete (global oder für eine Klasse). Muss ich den Destruktor manuell im Code aufrufen oder erledigt sich das beim bereinigen mit z.B. free().
Du musst den Destruktor selbst aufrufen, auch
free()gibt nur Speicher frei. Pass auf, dass du die C-Speicherverwaltung nicht mit der von C++ vermischst, also z.B. mitnewanforderst und mitfree()freigibst.Mit Templates kannst du dir aber eine Funktion schreiben:
template <typename T> void delete_obj(const T* ptr) { ptr->~T(); operator delete(ptr); }Siassei schrieb:
Kann ich irgendwie die Typinformationen speichern, damit ich folgendes machen kann
Nein, nicht wirklich. Du müsstest schon jeden Typen einzeln behandeln. Mit Templates liesse sich das vielleicht ein wenig komfortabler gestalten, aber schlussendlich musst du trotzdem schauen, dass jeder Typ richtig zerstört wird. Du solltest aber von solchen Ideen wegkommen, wenn du C++ programmierst. Hier ist der Objektersteller dafür verantwortlich, dass die Objekte korrekt zerstört werden. Genauso muss er sicherstellen, dass er Speicher wieder freigibt. Versuche, GarbageCollector-ähnliche Konstrukte nachzubauen, die ja so viel benutzerfreundlicher sind, haben selten etwas gebracht. Im Gegenteil, man nimmt dem Anwender die Kontrolle weg, schränkt ihn ein und sorgt für unangenehme Überraschungen.
Siassei schrieb:
Wie sieht es eigentlich bei Stukturen aus? Wenn ich mich nicht recht erinnere, gibt es zwei Sorten. C konforme und nicht C konforme Strukturen. Ersteres müsste ich doch mich free() einfach freigeben können, oder?
Mit was könnte ich das Überprüfen? In <type_traits> konnte ich nichts finden.
Das wäre
boost::is_pod. Aber das brauchst du nicht. Wenn du generisch (siehe oberes Beispiel) den Destruktor aufrufst, wird automatisch geschaut, ob der entsprechende Typ einen hat oder nicht. Auch beiintzum Beispiel wird der Aufruf einfach ignoriert.Siassei schrieb:
Fragen, über Fragen und ich finde keine Anworten

So, ich hoffe, ich konnte dir einigermassen helfen.
-
Servus,
danke für deine Antworten und ich hab nun einen Ansatz, denn ich weiter verfolge. Danke

Hier ist der Objektersteller dafür verantwortlich, dass die Objekte korrekt zerstört werden. Genauso muss er sicherstellen, dass er Speicher wieder freigibt. Versuche, GarbageCollector-ähnliche Konstrukte nachzubauen, die ja so viel benutzerfreundlicher sind, haben selten etwas gebracht. Im Gegenteil, man nimmt dem Anwender die Kontrolle weg, schränkt ihn ein und sorgt für unangenehme Überraschungen.
Das sehe ich etwas anders. Ein großer Nachteil der vorhandenen Lösungen ist die fehlende Fragmentierung des Speicherbereiches. Oder habe ich da was überlesen? Zudem verbraucht der delete-Operator auch ein paar Resourcen und das kann sich bei kritischen Prozessen negativ auswirken.
Eine gute Vorbereitung vor intensiven Aufgaben und das spätere Zusammenräumen liefert mit Sicherheit ein gutes Verhalten. Da nur das Ergebnis zählt und aufräumen kann man danach auch noch. Zumindestens solange genügend Platz da ist
Ich freue mich schon auf meine ersten Ergebnissen

Achja, das ganze ist ein Hobby und wird mit Sicherheit nicht produktiv von mir eingesetzt.Gruß
Thomas
-
Siassei schrieb:
Das sehe ich etwas anders. Ein großer Nachteil der vorhandenen Lösungen ist die fehlende Fragmentierung des Speicherbereiches.
Kapier ich nicht. Fragmentierung ist schlecht, wieso sollte das fehlen von ihr ein Nachteil sein? Und welche Lösungen meinst du denn?
Zudem verbraucht der delete-Operator auch ein paar Resourcen und das kann sich bei kritischen Prozessen negativ auswirken.
das delete musst du so oder so machen. da kommst du auch mit einem GC nicht drum herum.
Eine gute Vorbereitung vor intensiven Aufgaben und das spätere Zusammenräumen liefert mit Sicherheit ein gutes Verhalten. Da nur das Ergebnis zählt und aufräumen kann man danach auch noch. Zumindestens solange genügend Platz da ist

sowas ist mit GC aber nicht möglich.
Im Zweifelsfall weiss der Programmierer bescheid wann allokation und deallokation am sinnvollsten ist. Warum ihm diese Möglichkeit wegnehmen?
-
Siassei schrieb:
Eine gute Vorbereitung vor intensiven Aufgaben und das spätere Zusammenräumen liefert mit Sicherheit ein gutes Verhalten.
Das kann schon sein, aber das ist kein Garbage Collector. Wenn du dir einen Allokator schreibst, der viel Speicher auf einmal anfordert und dann die einzelnen Blöcke verteilt, ist das okay. Ob man dadurch einen Vorteil hat, hängt vom Anwendungsgebiet ab.
Ich meinte eher Konstrukte, die z.B. bei einem
newkeindeletemehr nötig machen (nein, nicht Smart-Pointer) und dann irgendwann dasdeleteselbst durchführen, womöglich noch mit Konzepten wie Referenzzählung. Dadurch ist man zum Beispiel auch gezwungen, Objekte auf dem Heap anzulegen, und hat eine Reihe anderer Einschränkungen, auf die man achten muss. Also der Versuch, C++ durch einen GC-Nachbau bequem zu machen, ist böse. Dann sollte man Java nehmen. Aber auch in C++ ist es möglich, auf höherer Abstraktionsebene zu programmieren, sodass man sich nicht ständig um Freigeben und Byte-Schiebereien kümmern muss. Containerklassen verfolgen diesen Ansatz, oder zum Beispiel eben Smart Pointers und das Konzept des RAII.Für deine Allokatorklasse kannst zum Beispiel Funktionen schreiben, die dir Speicher aus dem Pool beschaffen. Intern hast du Datenstrukturen, die die Adressen und Grössen von zuvor angefordertem Speicher beinhalten. Wenn du jetzt anforderst und nicht mehr genügend Speicher vorhanden ist, wird automatisch mehr allokiert. Zusätzlich könntest du Methoden anbieten, die viel Speicher aufs Mal anfordern, um nachher effizient zu sein. Oder Methoden, die allen unbenutzten Speicher freigeben. Und so weiter...
Dass die Allokatorklasse aber auch die Zerstörung und Konstruktion der Objekte selber nach Ermessen durchführt, scheint mir jedoch keine gute Lösung zu sein. Du könntest das vielleicht bei den Schnittstellenfunktionen implementieren, sodass beim Aufruf zuerst Speicher angefordert und dann konstruiert wird. Aber auf diese Weise kann man sich auch sicher sein, dass man beim Aufruf der Lösch-Methode den Destruktor aufruft - das ist teilweise wichtig für die Programmsemantik. Damit hast du auch das Problem nicht, Typen dynamisch zu speichern -
void*reicht schliesslich für rohen Speicher.
-
Nexus schrieb:
1.1 Es gibt kein Placement Delete.
3.1 Placement Delete gibt es wie gesagt nicht.
Natürlich gibt es placement delete.
Placement delete wird dann aufgerufen, wenn du versuchst ein Objekt mittels placement new anzulegen, und der ctor mit einer Exception beendet wird.
Dann hätte man nämlich keine Möglichkeit, den Speicher korrekt zurückzugeben, da man delete an der Stelle eben nicht selbst aufrufen kann.
Ich hoffe ich habe das soweit richtig in Erinnerung.
Muss die Stelle im Standard suchen, bin mir aber 99% sicher dass placement delete im 03er Standard definiert ist. Implementiert ist es auf jeden Fall in einigen Compilern.
-
hustbaer schrieb:
Nexus schrieb:
1.1 Es gibt kein Placement Delete.
3.1 Placement Delete gibt es wie gesagt nicht.
Natürlich gibt es placement delete.
Placement delete wird dann aufgerufen, wenn du versuchst ein Objekt mittels placement new anzulegen, und der ctor mit einer Exception beendet wird.
Dann hätte man nämlich keine Möglichkeit, den Speicher korrekt zurückzugeben, da man delete an der Stelle eben nicht selbst aufrufen kann.
Ich hoffe ich habe das soweit richtig in Erinnerung.
Muss die Stelle im Standard suchen, bin mir aber 99% sicher dass placement delete im 03er Standard definiert ist. Implementiert ist es auf jeden Fall in einigen Compilern.
Ähm und was soll placement delete machen? Placement new legt ein Objekt in rohem Speicher an, der schon vorher auf irgendeine Art besorgt wurde. Wenn der Ctor beim placement new eine Exception wirft, bleibt der rohe Speicher eben roher Speicher - und wenn die Exception nicht gefangen wird oder der Speicher per RAII freigegeben wird, dann ists ein Speicherleck. Die freigabe des rohen Speichers passiert mit dem Gegenstück zu der Funktion, die den rohen Speicher angefordert hat. Also z.B. free(), expliziter Aufruf
operator delete(nichtdelete!), allocator::deallocate() oder was auch immer.
Das läuft genau wie bei jeder anderen Exception auch, sprich es passiert das gleiche wie wenn man an der Stelle des placement new ein "throw XYException" geschrieben hätte.
Das Gegenstück zum placement new (Zerstörung des Objekts ohne Speicherfreigabe) ist der explizite Dtor-Aufruf.
-
Zudem muss es nicht sein, dass der Speicher, in dem das Objekt über Placement New konstruiert wird, mit
operator newangefordert wurde, nur Platz für 1 Objekt bietet und gerade am Anfang dieses Objekts beginnt. Das "Placement Delete" wüsste also gar nicht wie und was freigeben - mal abgesehen davon, dass Freigeben nicht die Aufgabe ist.
-
placement new beschränkt sich ja nicht auf die "void *" Variante. Ich kann ja eine eigene "placement new" Funktion definieren, die z.B. einen Zeiger auf einen Pool nimmt. Ala
void* operator new(size_t n, Pool* pool) { return pool->alloc(n); }Wenn ich damit jetzt ein Objekt anlege:
void foo() { Pool p; Foo* f = new (&p) Foo(); }Und angenommen der ctor von Foo wirft mir ne Exception. Wie soll ich dann jemals an den Wert kommen, den mein eigener placement new Operator zurückgegeben hat? Kann ich nicht, daher muss ich hier eine eigene placement delete Funktion definieren:
void operator delete(void* p, Pool* pool) { pool->release(p); }Der Compiler ist dann so nett, falls Foo::Foo() eine Exception werfen sollte, mir den zu meinem operator new passenden operator delete aufzurufen, so dass ich den Speicher wieder freigeben kann.
Die "vordefinierten" placement new und placement delete Funktionen sehen einfach so aus:
void* operator new (size_t n, void* p) { return p; } void operator delete(void*, void*) { }In Wirklichkeit gibt's mehr vordefinierte Varianten, aber das steht alles im Wikipedia Artikel, den man ruhig mal lesen sollte:
-
hustbaer schrieb:
Natürlich gibt es placement delete.
Placement delete wird dann aufgerufen, wenn du versuchst ein Objekt mittels placement new anzulegen, und der ctor mit einer Exception beendet wird.
Dann hätte man nämlich keine Möglichkeit, den Speicher korrekt zurückzugeben, da man delete an der Stelle eben nicht selbst aufrufen kann.
Ich hoffe ich habe das soweit richtig in Erinnerung.Das ist völlig korrekt.
Ich mag das zum Beispiel benutzen, wenn ich direkt in Container reinkonstruieren will.
void* operator new(size_t size,Stack<Foo>& s){ assert(size==8); s.alloc(); return s.top(); } void operator delete<void* p,Stack<Foo>& s){ assert(p==s.top()); s.free(); }hier wird das placement delete aufgerufen, wenn der ctor von foo eine exception wirft. irgendwas sinnvolles muß ja geschehen, um das s.alloc() wieder rückgängig zu machen, ein speicherloch hängenzulassen wäre nicht ok. une keiner sagt, daß man den speicher mit dem normalen delete löschen dürfte.
in der main mache ich nurStack<Foo> s; new(s) Foo(konstrukltorArgumente);das kann in vielerlei hinsicht ein netter trick sein. ich benutze es gelegentlich, um sachen wie streams in container zu stopfen oder auch mal, um kopierungen zu sparen.
-
Huch, schon wieder was gelernt. Das Konzept mit dem zugehörigen
operator deletewar mir eigentlich bekannt (ich hab die Operatoren schon mit__FILE__und__LINE__als zusätzliche Argumente benutzt). Bisher dachte ich jedoch, Placement New würde nur dienew-Version bezeichnen, die ein Objekt an eine rohe Speicherstelle konstruiert. Auch vom Namen her schien mir das noch sinnvoll (Objekt in Speicher platzieren)...
Auf jeden Fall danke für die Aufklärung!

-
Ist mir auch sehr neu dass man die anderen operator new und operator delete auch als "palcement" bezeichnet - mir sind bisher nur die Bezeichnungen "user defined new/delete" und "non-standard new/delete" untergekommen. Wieder was gelernt.