Design Frage zu eigener Stack Klasse
-
ja ich hab eine art memoryppol (Klasse) die ähnlich wie ein Stack funktioniert. Man kann also nur immer neue Elemente oben drauf legen (wie ein Stack) und nur immer das oberste Element entfernen (so wie push und pop)
Mein Memorypool alloziert einfach zu Beginn zb 2 MB in der jetzt Daten aller art reingelegt werden können.
Und ich will jetzt eben ein Objekt von TestClass dort reinlegen und das über den überladenen new operator von TestClass (class new)
Ich weiss aber nicht wirklich wie ich das tun kann. (Der Memorypool soll dabei natürlich nicht global sein und möglichst kein Singleton)
jemand eine Idee?
-
beuschl schrieb:
jemand eine Idee?
So könnte das ungefähr aussehen:
struct Foo { void* operator new(size_t, vector<Foo> pool) { pool.push_back(Foo()); return &pool.back(); } }; int main() { vector<Foo> memory_pool; Foo* a=new (memory_pool) Foo; }Hier mit einem vector als Möchtegernpool, weil ich deine Poolklasse nicht habe. Dies ist natürlich hochgefährlich, da der vector eventuell intern umkopiert, aber es soll ja nur ein Beispiel sein.
-
Bei einer Stackklasse würde ich eher die Methoden
pushundpopanbieten, ich denke es kann zu leicht passieren, dass jemand maldeleteauf ein Objekt anwendet, das nicht ganz oben liegt.@SeppJ: du nimmst den
vectorversehentlich als Kopie, das geht hier ganz derb schief.
-
also ich hab bei meiner Stack klasse auch nur push und pop um daten oben einzufügen/bzw zu löschen
die methoden funktionieren auch soweit (mit verschiedenen datentypen und klassen getestet)
nur das mit dem new overloading eienr Klasse geht noch immer nicht. Die Methode von SeppJ lieferte auf einmal extrem komische Ergebnise. Hatte einen frischen Pool angelegt mit 100 Bytes und diesen Pool dem new operator von testClass übergeben. Dann das stack.push(TestClass()) gemacht und auf einmal hatte ich nur mehr Blödsinn in den Variablen des Stack Pools stehn
EDIT: grad den post von ipsec gelesen! Das Problem war das was du eben sagtest mit der Kopie vom stack (vector) anstatt einer Referenz. Jetzt läuft es, danke!
-
Mit
std::vectormusst du trotzdem aufpassen. Wenn er vergrössert wird, können alle Zeiger auf seine Elemente ungültig werden.
-
beuschl schrieb:
also ich hab bei meiner Stack klasse auch nur push und pop um daten oben einzufügen/bzw zu löschen
Wir wissen nicht wie dieses push und pop aussieht. Du solltest aber darauf achten, dass Du dabei keine Objekte von Typ TestClass anlegst .. wie das Beispiel von SeppJ zeigt:
#include <iostream> #include <vector> struct Foo { Foo() { std::cout << "Foo[" << this << "] constructed" << std::endl; } Foo( const Foo& b ) { std::cout << "Foo[" << this << "] copied from [" << &b << "]" << std::endl; } void* operator new(size_t, std::vector<Foo>& pool ) { pool.push_back(Foo()); return &pool.back(); } }; int main() { std::vector<Foo> memory_pool; Foo* a=new (memory_pool) Foo; }man erhält als Ausgabe:
Foo[0012FD7F] constructed Foo[0012FAC7] copied from [0012FD7F] Foo[00366648] copied from [0012FAC7] Foo[00366648] constructedEs werden drei Objekte angelegt, aber vom Dritten wird zweimal ein Konstruktor durchlaufen.
operator new darf nur den Speicher besorgen, aber kein Objekt konstruieren. Der Return-Typ ist void* und nicht TextClass*! Bzw. operator new darf natürlich dann auch keinen Speicher zurückgeben, der bereits auf ein fertiges Objekt zeigt, denn dieser wird vom Konstruktor von TestClass wieder überschrieben. Das ist nur egal, wenn TestClass ein POD ist.
Gruß
Werner
-
Da das anscheinend etwas untergegangen ist: Nimm mein Beispiel nicht zu ernst, ich wollte dir nur die ungefähre Syntax zeigen, weil ich den Eindruck hatte, dass dies dein Problem ist. vector ist nun einmal kein memory-Pool und mein Missbrauch als solcher ist damit (wie schon gesagt) hoch gefährlich.
Im Nachhinein hätte ich wohl lieber std::allocator nehmen sollen:
#include <iostream> #include <memory> struct Foo { Foo() { std::cout << "Foo[" << this << "] constructed" << std::endl; } Foo( const Foo& b ) { std::cout << "Foo[" << this << "] copied from [" << &b << "]" << std::endl; } void* operator new(size_t, std::allocator<Foo>& pool ) { return pool.allocate(1); } }; int main() { std::allocator<Foo> memory_pool; Foo* a=new (memory_pool) Foo; }
-
beuschl schrieb:
ja ich hab eine art memoryppol (Klasse) die ähnlich wie ein Stack funktioniert. Man kann also nur immer neue Elemente oben drauf legen (wie ein Stack) und nur immer das oberste Element entfernen (so wie push und pop)
Mein Memorypool alloziert einfach zu Beginn zb 2 MB in der jetzt Daten aller art reingelegt werden können.
Und ich will jetzt eben ein Objekt von TestClass dort reinlegen und das über den überladenen new operator von TestClass (class new)
Ich weiss aber nicht wirklich wie ich das tun kann. (Der Memorypool soll dabei natürlich nicht global sein und möglichst kein Singleton)
Da hoffe ich, daß du genau weißt, was du damit vorhast:
- niemand garantiert dir, daß die Objekte stack-artig wieder freigegeben werden (zu jedem new gehört ein delete)
- wenn dein Pool aus dem Scope fällt, zerstört er alle Objekte, die du mit seiner Hilfe angelegt hast
- operator new() und operator delete() sollten immer paarweise auftreten (der aufgerufen wird, wenn der nachgeschaltete Konstruktor fehlschlägt)(und daß operator new mit rohem Speicher hantiert, wurde ja bereits erwähnt)
-
Die Syntax von new und delete überladen ist mir bekannt.
Auch die Probleme die beim hantieren mit meinen Pool entstehen könnenwas ich brauche ist eine lösung/idee, wie man das new überladen am besten macht, dass es a) funktioniert und b) auch eine möglichst gute/elegante lösung ist
a) ist ja bereits erfüllt mitSeppJ methode
aber b) nicht da wie Werner Salomon bereits sagte, new ja eigentlich nur Speicher anlegen soll und das Objekt dort dann konstruiert werden sollich hab den Quellcode meiner Klasse gerade nicht da aber push und pop sieht so aus
template <typedef T> void MemoryStack::push(T* data); void MemoryStack::pop();ich kann natürlich bei push noch ganz einfach machen, dass ein Pointer zurückgegeben wird, der zum Anfang des eingefügten Datenblocks (data) zeigt
-
Wenn du dich wirklich auf dieses push()/pop()-Interface beschränken willst, fällt mir schon keine Lösung ein, die a) erfüllt:
- operator new() arbeitet nicht mit fertigen Objekten, sondern mit nackten Speicherbereichen, die erst nach der Rückgabe initlialisiert werden (per Konstruktor)
- es gibt afaik keine Möglichkeit, explizit ein "placement delete" aufzurufen - die Anweisungdelete a;verwendet grundsätzlich den einfachen operator delete (der scheitern wird, weil deine Speicher nicht vom Standard-new stammt)Das heißt, du hast keine vernünftige Möglichkeit, die über den Pool erzeugten Objekte wieder freizugeben - das dürfte spätestens dann ins Auge gehen, wenn du mit RAII-Klassen arbeitest.
Nur mal aus Intersse: Warum bist du eigentlich der Meinung, daß dieses Vorgehen wirklich nützlich ist?
-
Um es etwas besser zu machen, kannst du das frisch erzeugte Objekt erstmal per explizitem Destruktoraufruf wieder löschen, so dass beim Überschreiben des Speicherbereiches wenigstens kein Leak entsteht. Sehr hübsch ist das natürlich immer noch nicht.
Besser: du erzeugst im
operator newgar kein Objekt vom Typ T, sondern vonboost::aligned_storage<sizeof(T), alignof(T)>::type(wenn du keinalignofhast, kannst du auchboost::alignment_of<T>::valuenehmen). Dieser Typ belegt einen Speicherbereich, der sich zum Speichern von Objekten von Typ T eignet, aber es wird kein Konstruktor aufgerufen. Du kannst also einfach einen Pointer auf den Bereich zurück eben und das Objekt darin konstruieren lassen.Mich würde aber davon abgesehen auch mal interessieren, warum du unbedingt den
operator newnehmen willst.
-
das ist schnell beantwortet:
Es ist die Aufgabenstellung einen Memorystack zu implementieren und dieses Stack mit irgendeiner Testklasse zu benutzen (indem man den new Operator überschreibt)
Das Ziel soll einfach sein, dass ich zb zu Beginn des Programms einen relativ großen Speicherblock reservieren kann und dort dann imemr wieder Objekte/Daten reingeschrieben/gelöscht werden. D.h dieser Speicherbereich kann fragmentiern etc was er will, man kann ihn einfach mit einem Befehl (delete) komplett wieder löschen oder mittels clear() wieder zurücksetzen.
Den Memorystack zu programmiern war jetzt nicht sonderlich schwer, nur die Aufgabenstellung mit den Operator new bereitet mir halt Kopfzerbrechen, da mir dazu auch keine Lösung eingefallen ist
Es ist übrigens nirgends die Rede, dass der delete Operator auch überladen werden soll. D.h der Benutzer muss sich darum kümmern, was er wann auf den Stack schreibt und es gegebenenfalls wieder in richtiger Reihenfolge vom Stack löschen.
-
Wer kommt denn auf die Idee, solche Aufgaben zu stellen?
beuschl schrieb:
Es ist übrigens nirgends die Rede, dass der delete Operator auch überladen werden soll. D.h der Benutzer muss sich darum kümmern, was er wann auf den Stack schreibt und es gegebenenfalls wieder in richtiger Reihenfolge vom Stack löschen.
Das kann er aber nicht wirklich - er kann bestenfalls explizit den Destruktor aufrufen und das Objekt vergessen (ist hässlich und hat imho im Anwendungscode nichts zu suchen) oder darauf vertrauen, daß der Stack das Objek schon irgendwann zerstört (was eine Weile brauchen dürfte, shließlich kann der Stack nicht selber feststellen, welche Elemente er nicht mehr braucht).
Ich würde bei so einem Ansatz eher auf die new/delete-Syntax verzichten und dem Nutzer ein eigenes Get()/Release()-Interface anbieten - das hat den Vorteil, daß du (a) nicht mit nacktem Speicher hantieren mußt, (b) nicht die C++ Speicherverwaltung austricksen mußt und (c) der Nutzer klar erkennen kann, welchen Speicher er wohin zurückgeben muß.PS: Dir ist hoffentlich klar, daß ein lokal liegender Memory-Stack nicht wirklich beim Programmstart gefüllt wird, sondern jedes Mal, wenn die umliegende Funktion aufgerufen wird.
ipsec schrieb:
Um es etwas besser zu machen, kannst du das frisch erzeugte Objekt erstmal per explizitem Destruktoraufruf wieder löschen, so dass beim Überschreiben des Speicherbereiches wenigstens kein Leak entsteht. Sehr hübsch ist das natürlich immer noch nicht.
Simmt, eine interessante Lösung, nur könnte es einigen Overhead mitbringen, wenn du ein Dummy-Objekt erzeugst und wieder freigibst, bevor du seinen Speicher zurückgibst.
-
gefüllt wird der stack nicht zu programmstart, aber im konstruktor vom stack, geb ich zb an dass der 2 mb groß sein soll. dann wird dort gleich mal dieser speicher reserviert und der benutzer kann solange objekte/daten dort ablegen bis der stack voll ist. dann muss er entweder daten wieder entfernen (pop) oder den stack komplett clearen oder deleten
ich finde die aufgabenstellung auch extrem ... vor allem das mit dem new überladen. macht für mich auch keinen sinn, speziell wegen dem delete problem vom objekt dann!
hilft trotzdem nichts, irgendeine halbwegs brauchbare lösung muss ich dafür finden (zumindest eine, die man halbwegs vernünftig begründen kann)