Allokationen zentralisieren



  • Hallo!

    Ich möchte eine MemoryManager Klasse schreiben, die Heap Allokationen zentralisiert. D.h. man kann sollte im Code nirgends mehr new aufrufen, sondern alle Allokationen nur noch per MemoryManager Klasse vornehmen. Den allokierten Blöcken will ich Metainfos wie z.B. einen beschreibenden String anhängen (hat vor allem Debuggründe).

    Jetzt frage ich mich, wie ich das am besten implementiere.
    Meine 2 ersten Ideen waren:
    1. Templates, also vielleicht sowas:

    Foo* f = MemoryMng.allocate<Foo>("my foo object");
    char* c = MemoryMng.allocateArray<char>(1000);
    

    Das Problem das ich hier habe: Wie übergebe ich Werte an Konstruktoren von Klassen? Also wenn Foo zb. 3 Paramter kriegt, wie übergebe ich die an den Foo Ctor?

    2. Wegen der Ctor Argument Problematik kam ich auf Ansatz 2: placement new:

    Foo* f = MemoryMng.allocate(sizeof(Foo), "my foo object"); // allokiert sizeof(foo) bytes. Kein Ctor Aufruf!
    f = new (f) Foo(a, b, c);
    

    Das sollte so ja klappen, oder? Muss ich beim placement new irgendwas beachten bzgl. Alignment?

    Was findet ihr besser bzw. gäbe es noch eine bessere Lösung?



  • Ich würde mich bei so einer Klasse an std::allocator<> orientieren (falls du das noch nicht kennst - das verwenden die STL-Container, um Speicher zu reservieren).

    Außerdem kann man benutzerdefinierte operator new() mit beliebigen Parametern definieren, so daß dann sowas möglich ist:

    Foo* f = new("my foo") Foo(x,y,z);
    


  • CStoll schrieb:

    Ich würde mich bei so einer Klasse an std::allocator<> orientieren (falls du das noch nicht kennst - das verwenden die STL-Container, um Speicher zu reservieren).

    Außerdem kann man benutzerdefinierte operator new() mit beliebigen Parametern definieren, so daß dann sowas möglich ist:

    Foo* f = new("my foo") Foo(x,y,z);
    

    Hm, ok, das klingt ja schon recht gut.
    Darf ich dann in meinem globalen void operator new(size_t size, const char* s); nicht mehr new benutzen, oder? (also das standard new ohne parameter gibts dann nicht mehr?)
    Muss ich dann in meinem globalen new malloc() benutzen? Aber dann wird ja nicht der Ctor aufgerufen? Oder ist der new Operator NUR für die Speicherbeschaffung gut und macht der NIE Ctor Aufrufe (es heißt ja immer: Malloc ruft keinen Ctor auf, new schon)



  • operator new() und operator delete() kümmern sich nur um die Speicherverwaltung, Konstruktoren bzw. Destruktoren werden unabhängig davon auf dem Speicherbereich angewendet, den du zurückgegeben hast.
    Ob du von deinem eigenen operator new() aus den Default-Operator aufrufen kannst, bin ich mir im Moment nicht sicher, auf jeden Fall solltest du den operator delete auch so schreiben, daß er mit dem von dir präperierten Speicher umgehen kann (und zwar sowohl den "normalen" als auch den Placement-Operator.

    PS: Mit welchem Compiler arbeitest du eigentlich? Beim MSVC erinnere ich mich noch an #define new DEBUG_NEW als Diagnosehilfe für Speicherlecks.



  • Ich verstehe nicht den Unterschied zwischen placement new und einer globalen überschreibung von operator new.
    Wenn ich den operator new so neu definiere:

    void* operator new(size_t size, const char* s);
    

    dann kann ich ihn doch so aufrufen:

    Foo* f = new ("Hallo") Foo(2,3, "test");
    

    Das ist aber jetzt kein placement new? Auf einer Seite habe ich nämlich gelesen, dass Placement new so aussieht: new (expression) Type(params);
    Nach dieser Definition wäre aber mein Aufruf ein placement new. Wann überschreibe ich jetzt placement new und wann einfach nur den globalen new operator?

    Noch eine Randfrage: Wenn operator new void* zurückgibt, wieso muss ich dann bei Foo* f = new("hallo") Foo; eigentlich nicht downcasten auf Foo*?



  • operator new ist lediglich für die Speicherbeschaffung zuständig, nicht für das Erzeugen des Objekts. new wiederrum beschafft sich intern den Speicher mit operator new und konstruiert dort das Objekt.
    Casten musst du also nicht, weil du operator new gar nicht direkt aufrufst. Das geschieht nur intern.

    Das placement-new ist ein Spezialfall des überschriebenen operator new . Es ist etwa so definiert:

    void* operator new(size_t, void* ptr) { return ptr; }
    

    Man kann es also ausnutzen, um in einen schon vorhandenen Speicherplatz ein Objekt zu konstruieren (anders bekommt man den Konstruktor nicht aufgerufen, das kann nur new ).



  • Alloka schrieb:

    Ich verstehe nicht den Unterschied zwischen placement new und einer globalen überschreibung von operator new.
    Wenn ich den operator new so neu definiere:

    void* operator new(size_t size, const char* s);
    

    dann kann ich ihn doch so aufrufen:

    Foo* f = new ("Hallo") Foo(2,3, "test");
    

    Das ist aber jetzt kein placement new? Auf einer Seite habe ich nämlich gelesen, dass Placement new so aussieht: new (expression) Type(params);
    Nach dieser Definition wäre aber mein Aufruf ein placement new.

    Das ist alles eine Frage der Definition - wenn du alle new-Varianten mit Zusatzparametern als Placement new bezeichnest, ist das eins. Wenn du nur die In-Place Konstruktion als Placement new bezeichnest ( Foo* p = malloc(sizeof(p));new(p)Foo(x,yz); ), ist es keins.

    Und wie ipsec schon erklärte: operator new arbeitet mit nacktem Speicher - hinter den Kulissen ruft new erst den entsprechenden operator new() auf, erledigt dann den Downcast und ruft den Konstruktor auf. Dieses Verhalten kannst du nicht beeinflussen - du kannst nur festlegen, woher der verwendete Speicher kommt.



  • Ok, langsam lichtet sich der Nebel etwas. 😃

    Ich weiß jetzt also, dass es ein keyword new gibt und einen operator new. Operator new kümmert sich nur um die Speicherallokierung. Keyword new ruft operator new auf, danach den Ctor und kümmert sich auch um den Downcast.

    Was mir hingegen nicht klar ist:
    1. Das hier ist ja placement new:

    void* p = malloc(sizeof(Foo));
    Foo* f = new (p) Foo(2);
    

    Hier erzeugt new also NICHT neuen Speicher sondern legt Foo nur in den vorhandenen Speicher. Kann ich das überhaupt überschreiben? Wie sollte so ein überschriebener globaler placement new überhaupt aussehen?

    2. ipsec meinte, placement new ist nur ein Spezialfall eines operator new. Das versteh ich nicht, denn placement new ist ja doch was anderes.
    Hier ruft keyword new erst operator new auf und der legt Speicher an. Danach Ctor Aufruf etc.

    Foo* f = new ("Hallo") Foo(2);
    

    Das hier sieht ähnlich aus, aber ist was anderes:

    Foo* f = new (p) Foo(2);
    

    Denn hier ruft keyword new operator new auf aber dieses mal wird operator new KEINEN Speicher allokieren, sondern sofort das Objekt im vorhandenen Speicher initialisieren. Bei einem placement new macht das keyword new also was anderes als bei einem standard operator new. Wie wird das unterschieden?



  • Ich habe dir die Implementierung des Placement-new schon gepostet. Das ist ein überschriebener operator new , der nicht wirklich Speicher allokiert, sondern nur so tut als ob und genau den Pointer als Beginn des Speicherbereichs zurückgibt, der als Parameter übergeben wurde.
    new kann nicht wissen, dass eigentlich gar keine Allokation stattfand, sondern konstruiert schön in den von operator new gelieferten Speicherberich das Objekt.



  • Danke! Du hast übrigens Post ipsec;)



  • new T(Parameter) ruft operator new und placement new auf. new(Speicher) T(Parameter) ist placement new. Du kannst das nicht überschreiben, das wird in <new> definiert. Für deine Allokationsklasse kannst du folgendes machen:

    1. Wenn du C++0x hast, geht so etwas:

    namespace memory
    {
    template<typename T, typename... Args> T *allocate(Args... args)
    {
        return new T(args...);
    }
    }
    

    2. Wenn nicht, geht je nach Anwendungsfall immer noch ein Define:

    #define NEW_1(Type, ...) CreateObj1(new Type(__VA_ARGS__))
    #define NEW_2(call) CreateObj1(new call)
    
    template<typename T> T *CreateObj1(T *ptr)
    {
        register_pointer_in_debug_database(ptr);
        return ptr;
    }
    


  • Darf ich dann in meinem globalen void operator new(size_t size, const char* s); nicht mehr new benutzen, oder? (also das standard new ohne parameter gibts dann nicht mehr?)

    Natürlich gibt's den normalen operator new dann noch, du kannst den Aufruf einfach weiterleiten (ohne den const char* Parameter natürlich, sonst wäre der Aufruf ja rekursiv).

    Allerdings halte ich das ganze Vorhaben für Unfug. Aber jeder wie er meinst.



  • Also wenn ich Probleme mit Speicherlöchern hätte, was ich aber nicht habe, würde ich in allen Dateien, die nicht wie vector.hpp ein berechtigtes Interesse an placement new haben,

    #define new new(__FILE__,__LINE__)
    

    machen.


Log in to reply