Kurze Verständnisfragen zu Allokatoren und dem "Stateless"


  • Administrator

    Allokatoren für die Standardbibliothek dürfen, soweit ich mich erinnere, keinen Status haben, wenn sie Kompilerunabhängig sein sollen, richtig? Wieso genau ist dem so?

    Und wäre es dann erlaubt, wenn alle Templateallokatoren vom gleichen Typ intern einen Zeiger auf ein globales Objekt haben, welches die Speicherverwaltung übernimmt? Also sowas wie ein globaler Speicherpool für einen Typ. So hätten alle Templateallokatoren ganz sicher den gleichen Status, da sie einfach nur einen Zeiger besitzen und alle Aufgaben an dieses Objekt weiterleiten.

    Danke im voraus.

    Grüssli



  • Soweit ich weiss ist das einzige Problem, dass Allokatoren kopiert werden (also mit operator = bzw. dem copy-ctor).
    Wenn du einen Zeiger verwendest, auf ein Objekt welches sich alle Allokatoren teilen, dann sollte das OK gehen.
    Das zweite Problem was du haben wirst, ist, dass Allokatoren per "rebind" auch für andere Typen verwendet werden.
    D.h. wenn du einen Allokator für T übergibst, dann muss allocator<T>::rebind<U> auch für Typ U funktionieren.

    Und im deutschen sagt man eher Zustand, nicht Status. Oder eben einfach "State".



  • Da ich mal annehme dass der "interne Zeiger" ein statisches Member ist gehört er zur Klasse, nicht zum einzelnen Allokator. Damit ist der Allokator weiterhin stateless.



  • Der Zeiger könnte auch non-static sein.
    Angenommen ich habe einen Pool-Allocator, dann hätte dieser z.B. einen shared_ptr auf einen Pool. Wobei es mehrere Pools geben kann.

    Nur der Pool wird beim Allocator kopieren eben nicht mit kopiert, sondern bloss der Zeiger auf den Pool.



  • hustbaer schrieb:

    Der Zeiger könnte auch non-static sein.
    Angenommen ich habe einen Pool-Allocator, dann hätte dieser z.B. einen shared_ptr auf einen Pool. Wobei es mehrere Pools geben kann.

    Nur der Pool wird beim Allocator kopieren eben nicht mit kopiert, sondern bloss der Zeiger auf den Pool.

    Wenn ich das richtig sehe kann es aber vorkommen, dass ein objekt, das mit "irgendeinem" Allokator allokiert wurde, mit "irgendeinem" anderen deallokiert wird. In dem Beispiel hätte der zweite Allokator ein Objekt zu deallokieren das nicht in seinem pool ist.



  • Hmmm...
    Das wäre dann natürlich doof 🙂
    Naja, dass das Standard-Library Allocator Konzept total "broken" ist, ist ja kein Geheimnis in der C++ Szene...


  • Administrator

    Dann müsste man also in jedem Speicherblock zum Beispiel sizeof(void*) zusätzliche Bytes speichern, welche dann einen Zeiger zum korrekten Pool beinhalten? Also auf das, was im Allokator gespeichert ist, darf man überhaupt nicht gehen? Seehr sinnvoll ...
    Dann hätte man die Methoden gleich statisch machen können und wieso kann man denn bitte ein Allokator Objekt dem Konstruktor übergeben? Was ist der Sinn dahinter?

    Wird dieses Konzept wohl jemals geändert werden? Die Rückwärtskompatibilität wird da wohl jedem Neustart ein Strich durch die Rechnung machen ...

    Grüssli



  • Ahhhh, die Erinnerung setzt ein... 🙂

    pumuckl schrieb:

    Wenn ich das richtig sehe kann es aber vorkommen, dass ein objekt, das mit "irgendeinem" Allokator allokiert wurde, mit "irgendeinem" anderen deallokiert wird. In dem Beispiel hätte der zweite Allokator ein Objekt zu deallokieren das nicht in seinem pool ist.

    Ich glaube das ist eine "Grauzone" im Standard bzw. dessen Auslegung, oder zumindest was existierende Implementierungen angeht.
    Allokatoren müssen nämlich soweit ich weiss den operator == implementieren, und zurückgeben, ob sie "kompatibel" sind. Also in meinem vorgeschlagenen Pool-Fall, ob sie den gleichen Pool verwenden.
    Beispiel aus der MSVC 9 Standard-Library, Implementierung von std::vector<T>:

    P.J. Plauger schrieb:

    void swap(_Myt& _Right)
    		{	// exchange contents with _Right
    		if (this == &_Right)
    			;	// same object, do nothing
    		else if (this->_Alval == _Right._Alval)
    			{	// same allocator, swap control information
    
     #if _HAS_ITERATOR_DEBUGGING
    			this->_Swap_all(_Right);
     #endif /* _HAS_ITERATOR_DEBUGGING */
    
    			this->_Swap_aux(_Right);
    
    			_STD swap(_Myfirst, _Right._Myfirst);
    			_STD swap(_Mylast, _Right._Mylast);
    			_STD swap(_Myend, _Right._Myend);
    			}
    		else
    			{	// different allocator, do multiple assigns
    			this->_Swap_aux(_Right);
    
    			_Myt _Ts = *this;
    
    			*this = _Right;
    			_Right = _Ts;
    			}
    		}
    

    Der "different allocator" Zweig verletzt hierbei auch die O(1) Garantie. Geht IMO auch nicht anders, da Allokatoren AFAIK nicht "no-throw assignable", und nicht "no-throw swappable" sein müssen. Dadurch kann man die Allokatoren nicht einfach tauschen, da es schief gehen könnte. Und nach dem "schief gehen" könnte ein versuchtes "Undo" ebenfalls schief gehen, und dann wäre man komplett angeschmiert.
    (Dass der *Allokator-Typ* gleicht ist, ist natürlich schon allein dadurch garantiert, dass Container mit unterchiedlichen Allokator-Typen, schon komplett unterschiedliche (und unverwandte) Klassen sind, also vec.swap(other) gar nie aufgerufen werden kann, wenn other einen unterschiedlichen Allokator-Typ verwendet)

    In anderen swap Funktionen, sowie den "splice", "merge", ... Funktion von "std::list" findet man ähnliche Konstrukte.

    Aber mal ganz davon abgesehen dass es vermutlich keine bessere Lösung gibt, als die Elemente in swap() in diesem "Spezialfall" zu kopieren... führt mich das ganze zur Annahme, dass die Standard Library sehrwohl garantiert, dass Objekte mit "dem selben" (=einem kompatiblen) Allokator freigegeben werden, über den sie auch angefordert wurden.

    ----

    Dravere schrieb:

    Dann müsste man also in jedem Speicherblock zum Beispiel sizeof(void*) zusätzliche Bytes speichern, welche dann einen Zeiger zum korrekten Pool beinhalten?

    Das wäre vermutlich ein gangbarer Weg.

    Dravere schrieb:

    Also auf das, was im Allokator gespeichert ist, darf man überhaupt nicht gehen?

    Wenn du maximale Portierbarkeit/Kompatibilität mit vielen Implementierungen haben willst, dann sicher nicht. Sonst u.U. schon. Wobei ich mich mit dieser Einschätzung durchaus irren kann.


Anmelden zum Antworten