Eigene Speicherallokation



  • Hallo zusammen, ich wollte zu Test- und Übungszwecken eine eigene Funktion schreiben, die Speicher allokiert und Objekte initialisiert. Intern greife ich dabei auf malloc() zu, um mir Speicher zu beschaffen.

    Konkret auf ein Beispiel angewandt, könnte man es ja so machen ( Logger ist eine Non-POD-Klasse, die ihre Methodenaufrufe dokumentiert):

    Logger* q = static_cast<Logger*>(malloc(sizeof(Logger)));
    	q->Logger::Logger();
    

    Dazu hätte ich auch gleich eine Frage: Wieso muss man die Klassenzugehörigkeit des Konstruktors explizit angeben? Also weshalb reicht q->Logger() nicht aus?

    Im Weiteren wollte ich das nun als Funktionstemplate realisieren:

    template <typename T>
    T* Allocate(const T& Object = T())
    {
    	T* Pointer = static_cast<T*>(malloc(sizeof(T)));
    	Pointer->T::T(Object);
    
    	return Pointer;
    }
    

    Doch merkwürdigerweise erhalte ich beim manuellen Konstruktoraufruf die Fehlermeldung, Logger enthalte kein Element namens T . Aber ich denke, in Templates werden die Vorkommen von T ersetzt?

    Abgesehen davon habe ich mir überlegt, dass meine manuelle Speicheranforderung gefährlich sein könnte, wenn Vererbung und Polymorphie im Spiel ist. Aber es geht nur ums Ausprobieren, ich wollte hierbei auch auf new verzichten. Und die zugehörige Freigabefunktion kommt später noch.



  • Die Form Pointer->T::T(Object); kannte ich bisher noch gar nicht, gibt's nen Grund, dass du kein Placement-new verwendest (was mir eher geeignet scheint)?



  • Badestrand schrieb:

    Die Form Pointer->T::T(Object); kannte ich bisher noch gar nicht

    Ich eigentlich auch nicht, ich hab beim Suchen im Internet herausgefunden, dass man das so machen muss.

    Badestrand schrieb:

    gibt's nen Grund, dass du kein Placement-new verwendest (was mir eher geeignet scheint)?

    Ja, der new-Operator besitzt zuviele Einschränkungen. Der Rückgabetyp muss void* und der erster Parametertyp size_t sein. Zudem verliert man Typinformationen.



  • Ach so, ich meinte eigentlich statt dem Pointer->T::T(Object); , also dann

    template <typename T>
    T* Allocate(const T& Object = T())
    {
        void* memory = malloc( sizeof(T) );
        T* pointer = new (memory) T( Object );
        return pointer;
    }
    

    PS: Placement new macht ja afaik auch nur den Konstruktor-Aufruf, also das was du wolltest.



  • Okay, das wäre eine Möglichkeit, vielen Dank.

    Existiert aber eine Alternative, bei der man ohne new auskommt? Wie gesagt ist das zu Experimentierzwecken und mich hätte es noch gewundert, wie ich manuell ein Objekt an der Stelle im Speicher initialisieren könnte.

    Denn auf ein konkretes Beispiel angewandt funktioniert ja der Konstruktoraufruf. Aber wieso ist das im Funktionstemplate nicht möglich (siehe erster Post)?



  • Nexus schrieb:

    Okay, das wäre eine Möglichkeit, vielen Dank.

    Existiert aber eine Alternative, bei der man ohne new auskommt? Wie gesagt ist das zu Experimentierzwecken und mich hätte es noch gewundert, wie ich manuell ein Objekt an der Stelle im Speicher initialisieren könnte.

    Denn auf ein konkretes Beispiel angewandt funktioniert ja der Konstruktoraufruf. Aber wieso ist das im Funktionstemplate nicht möglich (siehe erster Post)?

    Eventuell solltest du noch wissen, dass mit new zweierlei Dinge gemeint sein können:

    1. die Funktion operator new , die Überladen werden kann
    2. der Operator new , der nicht überladen werden kann.

    Der Operator new ist der den man in C++ aufruft, wenn man new in einer Anweisung schreibt (allgemein um neuen Speicher zu erhalten, oder für placement new etc.) Die Anweisung T* pt = new T* kann wie folgt aufgefasst werden:
    - aufruf von operator new mit sizeof(T) als erstem Argument und allen weiteren argumenten danach (z.B. eine Adresse bei placement new)
    - Konstruktion eines T-Objektes im erhaltenen Speicher
    - rückgabe des Pointers auf diesen Speicher

    Die Konstruktion, explizit einen Konstruktoraufruf zu starten, war mir bisher auch noch nicht untergekommen, vielmehr meine ich irgendwo gelesen zu haben dass das nicht geht, also kein korrektes C++ ist. Zumindest über placement new kommst du also nicht hinweg, allerdings macht das im Grunde nichts anderes als das was du willst: die FUnktion operator new mit pointer-parameter (die bei placement new aufgerufen wird) macht normalerweise nichts anderes als den übergebenen Pointer wieder zurückzugeben, so dass der Operator new nichts weiteres zu tun hat als eben die Konstruktion des Objektes.
    (funktioniert übrigens bei new[] genauso)

    abgesehn davon sollte dir bewusst sein, dass du den Speicher auch so wieder Freigeben musst wie du ihn besorgt hast, in dem Fall also mit free:

    template <class T>
    void Destroy(T* pt)
    {
      pt->~T(); //Manueller Destruktor Aufruf als Pendant zum Placement new
      free(pt);
    };
    

    /edit: informative Lektüre zum Thema ist einerseits das Kapitel über small object allocation im Modern C++ Design, zum anderen könntest du den Standard-Allokatoren deiner STL-Implementation etwas unter die Röcke schauen, wie die das regeln 😉



  • pumuckl schrieb:

    Die Konstruktion, explizit einen Konstruktoraufruf zu starten, war mir bisher auch noch nicht untergekommen, vielmehr meine ich irgendwo gelesen zu haben dass das nicht geht, also kein korrektes C++ ist. Zumindest über placement new kommst du also nicht hinweg

    Vielleicht ist es nicht Standard-C++ und eventuell undefiniert. Ich hab auch nicht vor, es weiter zu verwenden, aber ich will es einmal implementiert haben, dann bin ich glücklich. 🙂

    Und dass es prinzipiell geht, habe ich ja in meinem ersten Post gezeigt:

    Logger* q = static_cast<Logger*>(malloc(sizeof(Logger)));
    	q->Logger::Logger();
    

    Aber ich kann mir nicht erklären, dass die Template-Version davon nicht funktioniert, weil der Typ T anscheinend nicht ersetzt wird und ich deshalb eine Fehlermeldung ( Logger besitze kein Element T ) erhalte. Kann mir jemand erklären, wieso das nicht funktioniert?

    template <typename T>
    T* Allocate(const T& Object = T())
    {
    	T* Pointer = static_cast<T*>(malloc(sizeof(T)));
    	Pointer->T::T(Object);
    
    	return Pointer;
    }
    

    Es sollte aber schon irgendwie gehen. Im Notfall bin ich stur und mach es mit Makros... :p

    pumuckl schrieb:

    abgesehn davon sollte dir bewusst sein, dass du den Speicher auch so wieder Freigeben musst wie du ihn besorgt hast, in dem Fall also mit free:

    Ja, genau so eine Funktion hab ich inzwischen auch schon geschrieben. Und zu Beginn sagte ich ja, diese würde noch implementiert werden. 😉



  • Nexus schrieb:

    Aber ich kann mir nicht erklären, dass die Template-Version davon nicht funktioniert, weil der Typ T anscheinend nicht ersetzt wird und ich deshalb eine Fehlermeldung ( Logger besitze kein Element T ) erhalte. Kann mir jemand erklären, wieso das nicht funktioniert?

    Das liegt vermutlich daran, dass bei Templates nur die Typbezeichner ersetzt werden. Konstruktoren sind hingegen Methoden, die allerdings den gleichen Namen haben. Aus pointer->T::T() macht der Compiler also durch die Ersetzung des Typs ein pointer->Logger::T().
    Um das wirklich umzusetzen müsstest du also wirklich zu Makros greifen.

    Oder du sagst dir, dass es genug ist, es versucht zu haben und dabei festgestellt zu haben dass das kein Standard-C++ ist und nur mit Makros. Ich würd mir nicht anmaßen sowas hinzuferkeln, man hat ja schließlich doch noch ein bisschen Selbstachtung und Anstand 😉



  • pumuckl schrieb:

    Oder du sagst dir, dass es genug ist, es versucht zu haben und dabei festgestellt zu haben dass das kein Standard-C++ ist und nur mit Makros. Ich würd mir nicht anmaßen sowas hinzuferkeln, man hat ja schließlich doch noch ein bisschen Selbstachtung und Anstand 😉

    Makros sind aber krass. 😃
    Nun gut, dann mach ichs wenigstens noch mit Placement New. Und die Makro-Lösung muss ich auch probieren, hehe.

    Aber das mit den Templates versteh ich trotzdem nicht ganz. Bei STL-Containern werden auch Funktionen übergeben, die dann entsprechend eingesetzt werden. Und Folgendes geht ja auch:

    Pointer->~T();
    

    Wieso wird das hingenommen, der Aufruf eines Konstruktors jedoch nicht? Niemand ist ja so beschränkt, als Templateargument einen Bezeichner zu wählen, der sich mit den Methoden der Klasse überschneidet. Welchen Sinn hat es also, T() als Methodenaufruf nicht zu ersetzen? 🙄

    Und ist Placement New in jedem Falle definiertes Verhalten (vorausgesetzt, es wurde genügend Speicherplatz allokiert)? Werden automatisch die richtigen Konstruktoren aufgerufen, auch bei Vererbung?



  • Nexus schrieb:

    Und ist Placement New in jedem Falle definiertes Verhalten (vorausgesetzt, es wurde genügend Speicherplatz allokiert)?

    Solang du incht in irgendwelche Alignmentprobleme läufst ja.

    Nexus schrieb:

    Werden automatisch die richtigen Konstruktoren aufgerufen, auch bei Vererbung?

    Es wird der Konstruktor des Typs aufgerufen, für den du das new aufrufst. Und der ruft natürlich die Konstruktoren aller geerbten Klassen auf, wie gewöhnlich.


Log in to reply