polymorphic_allocator: kiedy i dlaczego powinienem go używać?

122

Oto dokumentacja dotycząca cppreference , tutaj jest robocza wersja robocza.

Muszę przyznać, że nie rozumiałem, jaki jest prawdziwy cel polymorphic_allocatori kiedy / dlaczego / jak mam go używać.
Na przykład pmr::vectorma następujący podpis:

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

Co polymorphic_allocatoroferuje ta oferta? Co std::pmr::vectoroferuje również w odniesieniu do staromodnych std::vector? Co mogę teraz zrobić, czego nie mogłem zrobić do tej pory?
Jaki jest prawdziwy cel tego podzielnika i kiedy właściwie powinienem go używać?

skypjack
źródło
1
Próbują przezwyciężyć pewne allocator<T>nieodłączne problemy . Więc zobaczysz w tym wartość, jeśli często używasz alokatorów.
edmz
2
Odpowiedni dokument .
edmz

Odpowiedzi:

103

Cytat wyboru z cppreference:

Ten polimorfizm w czasie wykonywania pozwala obiektom używającym polymorphic_allocator zachowywać się tak, jakby używały różnych typów alokatorów w czasie wykonywania pomimo identycznego statycznego typu alokatora

Problem z „zwykłymi” alokatorami polega na tym, że zmieniają one typ kontenera. Jeśli chcesz mieć vectorokreślony alokator, możesz skorzystać z Allocatorparametru szablonu:

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

Problem polega teraz na tym, że ten wektor nie jest tego samego typu, co wektor z innym alokatorem. Nie można go przekazać do funkcji, która wymaga na przykład domyślnego wektora alokatora, ani przypisać dwóch wektorów z innym typem alokatora do tej samej zmiennej / wskaźnika, np .:

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

Alokator polimorficzny to pojedynczy typ alokatora z elementem, który może definiować zachowanie alokatora poprzez dynamiczne wysyłanie, a nie poprzez mechanizm szablonu. Pozwala to na posiadanie kontenerów, które używają określonej, dostosowanej alokacji, ale które są nadal powszechnego typu.

Dostosowanie zachowania podzielnika odbywa się poprzez nadanie alokatorowi 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

Główny pozostały problem, jak to widzę, polega na tym, że std::pmr::kontener nadal nie jest zgodny z odpowiadającym mu std::kontenerem korzystającym z domyślnego alokatora. Podczas projektowania interfejsu, który współpracuje z kontenerem, musisz podjąć pewne decyzje:

  • czy jest prawdopodobne, że przekazany kontener może wymagać przydziału niestandardowego?
  • jeśli tak, czy powinienem dodać parametr szablonu (aby umożliwić dowolne przydzielanie), czy też powinienem nakazać użycie alokatora polimorficznego?

Rozwiązanie szablonowe pozwala na zastosowanie dowolnego alokatora, w tym alokatora polimorficznego, ale ma inne wady (rozmiar wygenerowanego kodu, czas kompilacji, kod musi być ujawniony w pliku nagłówkowym, możliwość dalszego „zanieczyszczenia typu”, co powoduje dalsze wypychanie problemu na zewnątrz). Polimorfizmu roztwór podzielnik z drugiej strony, który dyktuje polimorficzny podzielnik muszą być użyte. To wyklucza używanie std::kontenerów, które używają domyślnego alokatora i mogą mieć konsekwencje dla interakcji ze starszym kodem.

W porównaniu ze zwykłym alokatorem, polimorficzny alokator ma pewne drobne koszty, takie jak narzut pamięci wskaźnika memory_resource (który jest najprawdopodobniej nieistotny) oraz koszt wysyłania funkcji wirtualnej do alokacji. Tak naprawdę głównym problemem jest prawdopodobnie brak kompatybilności ze starszym kodem, który nie używa alokatorów polimorficznych.

davmac
źródło
2
Czy więc układ binarny dla std::pmr::klas będzie prawdopodobnie inny?
Euri Pinhollow
12
@EuriPinhollow, nie możesz reinterpret_castmiędzy a std::vector<X>i std::pmr::vector<X>, jeśli o to pytasz.
davmac
4
W prostych przypadkach, w których zasoby pamięci nie zależą od zmiennej środowiska wykonawczego, dobry kompilator zdewirtualizuje się, a otrzymasz polimorficzny alokator bez dodatkowych kosztów (z wyjątkiem przechowywania wskaźnika, co naprawdę nie stanowi problemu). Pomyślałem, że warto o tym wspomnieć.
DeiDei,
1
@ Yakk-AdamNevraumont " std::pmr::kontener nadal nie jest zgodny z równoważnym std::kontenerem korzystającym z domyślnego alokatora" . Nie ma też zdefiniowanego operatora przypisania z jednego do drugiego. W razie wątpliwości wypróbuj to: godbolt.org/z/Q5BKev (kod nie jest dokładnie taki, jak powyżej, ponieważ gcc / clang mają polimorficzne klasy alokacji w „eksperymentalnej” przestrzeni nazw).
davmac,
1
@davmac Ach, więc nie ma template<class OtherA, std::enable_if< A can be constructed from OtherA > vector( vector<T, OtherA>&& )konstruktora. Byłem niepewny i nie wiedziałem, gdzie znaleźć kompilator zgodny z protokołem pmr zgodnym z TS.
Yakk - Adam Nevraumont,
33

polymorphic_allocatorjest do niestandardowego alokatora, podobnie jak std::functiondo bezpośredniego wywołania funkcji.

Po prostu pozwala na użycie podzielnika z kontenerem bez konieczności decydowania w momencie deklaracji, który z nich. Więc jeśli masz sytuację, w której więcej niż jeden podzielnik byłby odpowiedni, możesz użyć polymorphic_allocator.

Może chcesz ukryć, który alokator jest używany do uproszczenia interfejsu, a może chcesz mieć możliwość zamiany go na różne przypadki uruchomieniowe.

Najpierw potrzebujesz kodu, który wymaga alokatora, a następnie musisz mieć możliwość zamiany używanego, przed rozważeniem wektora pmr.

Yakk - Adam Nevraumont
źródło
7

Wadą alokatorów polimorficznych jest to, że polymorphic_allocator<T>::pointerzawsze są sprawiedliwe T*. Oznacza to, że nie możesz ich używać z fantazyjnymi wskazówkami . Jeśli chcesz zrobić coś takiego, jak umieszczenie elementów a vectorwe współdzielonej pamięci i dostęp do nich przez boost::interprocess::offset_ptrs , musisz użyć do tego zwykłego, starego niepolimorficznego alokatora.

Tak więc, chociaż alokatory polimorficzne pozwalają na zróżnicowanie zachowania alokacji bez zmiany statycznego typu kontenera, ograniczają to, czym jest alokacja .

Maxpm
źródło
2
To jest kluczowy punkt i wielki kłopot. Artykuł Arthura O'Dwyera „ Towards sensful fancy pointers” bada obszar, podobnie jak jego książka „Mastering the c ++ 17 STL”
patrz
czy możesz podać prawdziwy przykład użycia alokatora polimorficznego?
darune