polymorphic_allocator: wann und warum sollte ich es verwenden?

120

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

Ich muss zugeben, dass ich nicht verstanden habe, was der eigentliche Zweck ist polymorphic_allocatorund wann / warum / wie ich es verwenden soll.
Als Beispiel pmr::vectorhat der folgende Signatur:

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

Was macht das polymorphic_allocatorAngebot? Was bietet das std::pmr::vectorAngebot auch in Bezug auf das Altmodische std::vector? Was kann ich jetzt tun, wo ich bis jetzt nicht konnte?
Was ist der eigentliche Zweck dieses Allokators und wann sollte ich ihn tatsächlich verwenden?

Skypjack
quelle
1
Sie versuchen, einige Probleme zu überwinden, die von allocator<T>Natur aus bestehen. Sie werden also einen Wert darin sehen, wenn Sie häufig Allokatoren verwenden.
Edmz
2
Relevantes Papier .
Edmz

Antworten:

101

Auswahl Zitat aus cppreference:

Dieser Laufzeitpolymorphismus ermöglicht es Objekten, die polymorphic_allocator verwenden, sich so zu verhalten, als ob sie zur Laufzeit trotz des identischen statischen Allokatortyps unterschiedliche Allokatortypen verwenden würden

Das Problem bei "regulären" Allokatoren ist, dass sie den Typ des Containers ändern. Wenn Sie einen vectormit einem bestimmten Allokator möchten , können Sie den AllocatorVorlagenparameter verwenden:

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

Das Problem ist nun, dass dieser Vektor nicht vom gleichen 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 Variablen mit einem anderen Zuordnungstyp 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 haben, die eine bestimmte, benutzerdefinierte Zuordnung verwenden, aber immer noch einen gemeinsamen Typ haben.

Die Anpassung des Allokatorverhaltens erfolgt, indem dem Allokator Folgendes zugewiesen wird 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 verbleibende Hauptproblem ist meines Erachtens, dass ein Container mit dem Standard-Allokator std::pmr::immer noch nicht mit dem entsprechenden std::Container kompatibel ist . 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 Vorlagenparameter hinzufügen (um beliebige Allokatoren zuzulassen) oder die Verwendung eines polymorphen Allokators vorschreiben?

Eine Vorlagenlösung ermöglicht jeden Allokator, einschließlich eines polymorphen Allokators, hat jedoch andere Nachteile (generierte Codegröße, Kompilierungszeit, Code muss in der Header-Datei verfügbar gemacht werden, Potenzial für weitere "Typkontamination", die das Problem immer weiter nach außen drückt). Eine polymorphe Allokatorlösung schreibt andererseits vor, dass ein polymorpher Allokator verwendet werden muss . Dies schließt die Verwendung von std::Containern aus, die den Standardzuweiser verwenden, und kann Auswirkungen auf die Schnittstelle mit Legacy-Code haben.

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

Davmac
quelle
2
Ist es also std::pmr::sehr wahrscheinlich, dass das binäre Layout für Klassen unterschiedlich ist?
Euri Pinhollow
12
@EuriPinhollow kannst du nicht reinterpret_castzwischen a std::vector<X>und std::pmr::vector<X>, wenn du das fragst .
Davmac
4
In einfachen Fällen, in denen die Speicherressource nicht von einer Laufzeitvariablen abhängt, wird ein guter Compiler devirtualisiert und Sie erhalten einen polymorphen Allokator ohne zusätzliche Kosten (außer zum Speichern des Zeigers, was wirklich kein Problem darstellt). Ich fand es erwähnenswert.
DeiDei
1
@ Yakk-AdamNevraumont "Ein std::pmr::Container ist std::mit dem Standard -Allokator immer noch nicht mit dem entsprechenden Container kompatibel . " Es ist auch kein Zuweisungsoperator von einem zum anderen definiert. Probieren Sie es im Zweifelsfall aus: godbolt.org/z/Q5BKev (Code ist nicht genau wie oben, da gcc / clang die polymorphen Zuordnungsklassen in einem "experimentellen" Namespace haben).
Davmac
1
@davmac Ah, es gibt also keinen template<class OtherA, std::enable_if< A can be constructed from OtherA > vector( vector<T, OtherA>&& )Konstruktor. Ich war mir nicht sicher und wusste nicht, wo ich einen Compiler mit TS-kompatiblem pmr finden sollte.
Yakk - Adam Nevraumont
33

polymorphic_allocatorist für einen benutzerdefinierten Allokator wie std::functionfür einen direkten Funktionsaufruf.

Sie können einfach einen Allokator für Ihren 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 verwenden polymorphic_allocator.

Vielleicht möchten Sie ausblenden, welcher Allokator zur Vereinfachung Ihrer Benutzeroberfläche verwendet wird, oder Sie möchten ihn für verschiedene Laufzeitfälle austauschen können.

Zuerst benötigen Sie Code, der einen Allokator benötigt, dann müssen Sie in der Lage sein, den verwendeten Code auszutauschen, bevor Sie den pmr-Vektor berücksichtigen.

Yakk - Adam Nevraumont
quelle
7

Ein Nachteil von polymorphen Allokatoren ist, dass dies polymorphic_allocator<T>::pointerimmer gerecht ist T*. Das heißt, Sie können sie nicht mit ausgefallenen Zeigern verwenden . Wenn Sie beispielsweise Elemente von a vectorim gemeinsam genutzten Speicher platzieren und über boost::interprocess::offset_ptrs darauf zugreifen möchten, müssen Sie dafür einen normalen alten nicht polymorphen Allokator verwenden.

Obwohl Sie mit polymorphen Allokatoren das Zuordnungsverhalten variieren können, ohne den statischen Typ eines Containers zu ändern, begrenzen sie die Zuordnung .

Maxpm
quelle
2
Dies ist ein wichtiger Punkt und ein großer Mist. Arthur O'Dwyers Towards bedeutungsvolles, ausgefallenes Zeigerpapier erkundet das Gebiet, ebenso wie sein Buch "Mastering the c ++ 17 STL"
sehe
Können Sie einen realen Anwendungsfall für die Verwendung eines polymorphen Allokators angeben?
Darune