web-dev-qa-db-de.com

polymorphic_allocator: wann und warum sollte ich es verwenden?

hier ist die Dokumentation zu cppreference , hier ist der Arbeitsentwurf.

Ich muss zugeben, dass ich nicht verstanden habe, was der wahre Zweck von polymorphic_allocator Ist und wann/warum/wie ich es verwenden sollte.
Als Beispiel hat das pmr::vector die folgende Signatur:

namespace pmr {
    template <class T>
    using vector = std::vector<T, polymorphic_allocator<T>>;
}

Was bietet der polymorphic_allocator? Was bietet der std::pmr::vector Auch in Bezug auf den altmodischen std::vector? Was kann ich jetzt tun, wo ich es bisher nicht konnte?
Was ist der eigentliche Zweck dieses Allokators und wann sollte ich ihn tatsächlich verwenden?

77
skypjack

Auswahlzitat von cppreference:

Dieser Laufzeit-Polymorphismus ermöglicht es Objekten, die polymorphic_allocator verwenden, sich so zu verhalten, als ob sie zur Laufzeit unterschiedliche Zuordnungstypen verwendet hätten, obwohl der statische Zuordnungstyp identisch ist

Das Problem bei "normalen" Zuweisern ist, dass sie den Typ des Containers ändern. Wenn Sie ein vector mit einem bestimmten Zuweiser möchten, können Sie den Vorlagenparameter Allocator verwenden:

auto my_vector = std::vector<int,my_allocator>();

Das Problem ist nun, dass dieser Vektor nicht derselbe Typ ist wie ein Vektor mit einem anderen Allokator. Sie können es nicht an eine Funktion übergeben, für die beispielsweise ein Standardzuweisungsvektor erforderlich ist, oder zwei Vektoren mit einem anderen Zuweisungstyp derselben Variablen/demselben Zeiger zuweisen, z.

auto my_vector = std::vector<int,my_allocator>();
auto my_vector2 = std::vector<int,other_allocator>();
auto vec = my_vector; // ok
vec = my_vector2; // error

Ein polymorpher Allokator ist ein einzelner Allokatortyp mit einem Element, das das Allokatorverhalten über den dynamischen Versand und nicht über den Vorlagenmechanismus definieren kann. Auf diese Weise können Sie Container erstellen, die eine bestimmte, benutzerdefinierte Zuordnung verwenden, jedoch immer noch von einem gemeinsamen Typ sind.

Die Anpassung des Zuweiserverhaltens erfolgt, indem dem Zuweiser ein std::memory_resource *:

// define allocation behaviour via a custom "memory_resource"
class my_memory_resource : public std::pmr::memory_resource { ... };
my_memory_resource mem_res;
auto my_vector = std::pmr::vector<int>(0, &mem_res);

// define a second memory resource
class other_memory_resource : public std::pmr::memory_resource { ... };
other_memory_resource mem_res_other;
auto my_other_vector = std::pmr::vector<int>(0, &mes_res_other);

auto vec = my_vector; // type is std::pmr::vector<int>
vec = my_other_vector; // this is ok -
      // my_vector and my_other_vector have same type

Das wichtigste verbleibende Problem, wie ich es sehe, ist, dass ein std::pmr:: Container ist immer noch nicht kompatibel mit dem entsprechenden std:: Container mit dem Standard-Allokator. Sie müssen einige Entscheidungen treffen, wenn Sie eine Schnittstelle entwerfen, die mit einem Container funktioniert:

  • ist es wahrscheinlich, dass der übergebene Container eine benutzerdefinierte Zuordnung erfordert?
  • wenn ja, sollte ich einen Template-Parameter hinzufügen (um willkürliche Allokatoren zuzulassen) oder die Verwendung eines polymorphen Allokators vorschreiben?

Eine Template-Lösung erlaubt any Allokator, einschließlich eines polymorphen Allokators, hat aber andere Nachteile (generierte Codegröße, Kompilierungszeit, Code muss in der Header-Datei verfügbar gemacht werden, potenzielle weitere "Typenkontamination", die immer weiter zunimmt das Problem äußerlich). Eine polymorphe Allokatorlösung schreibt andererseits vor, dass ein polymorpher Allokator muss verwendet werden muss. Dies schließt die Verwendung von std:: -Container, die den Standardzuweiser verwenden und möglicherweise Auswirkungen auf die Schnittstelle mit altem Code haben.

Verglichen mit einem regulären Allokator hat ein polymorpher Allokator einige geringfügige Kosten, wie z. B. den Speicheraufwand für den Zeiger memory_resource (der höchstwahrscheinlich vernachlässigbar ist) und die Kosten für den Versand virtueller Funktionen für Allokationen. Das Hauptproblem ist wahrscheinlich die mangelnde Kompatibilität mit Legacy-Code, der keine polymorphen Allokatoren verwendet.

66
davmac

polymorphic_allocator geht an einen benutzerdefinierten Zuweiser als std::function ist ein direkter Funktionsaufruf.

Sie können einfach einen Allokator mit Ihrem Container verwenden, ohne zum Zeitpunkt der Deklaration entscheiden zu müssen, welcher. Wenn Sie also eine Situation haben, in der mehr als ein Allokator angemessen wäre, können Sie polymorphic_allocator.

Vielleicht möchten Sie verbergen, welcher Allokator zur Vereinfachung Ihrer Benutzeroberfläche verwendet wird, oder Sie möchten in der Lage sein, ihn für verschiedene Laufzeitfälle auszutauschen.

Zuerst benötigen Sie Code, der einen Allokator benötigt, dann müssen Sie in der Lage sein, den verwendeten auszutauschen, bevor Sie pmr vector in Betracht ziehen.

Ein Nachteil von polymorphen Allokatoren ist, dass polymorphic_allocator<T>::pointer ist immer nur T*. Das heißt, Sie können sie nicht mit ausgefallenen Zeigern verwenden. Wenn Sie so etwas wie das Platzieren von Elementen eines vector im gemeinsam genutzten Speicher und den Zugriff darauf über boost::interprocess::offset_ptrs , Sie müssen dafür einen normalen alten nicht-polymorphen Allokator verwenden.

Obwohl polymorphe Zuweiser das Zuweisungsverhalten variieren lassen , ohne den statischen Typ eines Containers zu ändern, begrenzen sie die Zuweisung .

2
Maxpm