polymorphic_allocator: quando e perché dovrei usarlo?


122

Ecco la documentazione su cppreference , ecco la bozza di lavoro.

Devo ammettere che non ho capito qual è il vero scopo polymorphic_allocatore quando / perché / come dovrei usarlo.
Ad esempio, pmr::vectorha la seguente firma:

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

Cosa polymorphic_allocatoroffre l' offerta? Cosa std::pmr::vectoroffre anche nei confronti del vecchio stile std::vector? Cosa posso fare ora che non sono stato in grado di fare fino ad ora?
Qual è il vero scopo di quell'allocatore e quando dovrei usarlo effettivamente?


1
Cercano di superare alcuni problemi allocator<T>intrinsecamente. Quindi vedrai il valore in esso se usi frequentemente gli allocatori.
edmz

2
Carta pertinente .
edmz

Risposte:


103

Citazione scelta da cppreference:

Questo polimorfismo di runtime consente agli oggetti che utilizzano polymorphic_allocator di comportarsi come se usassero diversi tipi di allocatore in fase di esecuzione nonostante lo stesso tipo di allocatore statico

Il problema con gli allocatori "normali" è che cambiano il tipo di contenitore. Se vuoi un vectorcon un allocatore specifico, puoi utilizzare il Allocatorparametro template:

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

Il problema ora è che questo vettore non è dello stesso tipo di un vettore con un allocatore diverso. Non è possibile passarlo a una funzione che richiede un vettore di allocatore predefinito, ad esempio, o assegnare due vettori con un diverso tipo di allocatore alla stessa variabile / puntatore, ad esempio:

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

Un allocatore polimorfico è un singolo tipo di allocatore con un membro che può definire il comportamento dell'allocatore tramite invio dinamico anziché tramite il meccanismo del modello. Ciò consente di avere contenitori che utilizzano un'allocazione specifica e personalizzata, ma che sono comunque di un tipo comune.

La personalizzazione del comportamento dell'allocatore viene eseguita assegnando all'allocatore 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

Il problema principale rimanente, a mio avviso, è che un std::pmr::contenitore non è ancora compatibile con il std::contenitore equivalente che utilizza l'allocatore predefinito. È necessario prendere alcune decisioni nel momento in cui si progetta un'interfaccia che funzioni con un contenitore:

  • è probabile che il contenitore passato possa richiedere un'allocazione personalizzata?
  • in caso affermativo, devo aggiungere un parametro modello (per consentire allocatori arbitrari) o devo imporre l'uso di un allocatore polimorfico?

Una soluzione modello consente qualsiasi allocatore, incluso un allocatore polimorfico, ma presenta altri inconvenienti (dimensione del codice generato, tempo di compilazione, codice deve essere esposto nel file di intestazione, potenziale ulteriore "contaminazione del tipo" che continua a spingere il problema all'esterno). Una soluzione di allocatore polimorfico d'altra parte impone che deve essere utilizzato un allocatore polimorfico . Ciò preclude l'utilizzo di std::contenitori che utilizzano l'allocatore predefinito e potrebbe avere implicazioni per l'interfacciamento con il codice legacy.

Rispetto a un allocatore normale, un allocatore polimorfico ha alcuni costi minori, come l'overhead di archiviazione del puntatore memory_resource (che è molto probabilmente trascurabile) e il costo dell'invio di funzioni virtuali per le allocazioni. Il problema principale, in realtà, è probabilmente la mancanza di compatibilità con il codice legacy che non utilizza allocatori polimorfici.


2
Quindi, è std::pmr::molto probabile che il layout binario per le classi sia diverso?
Euri Pinhollow

12
@EuriPinhollow non puoi reinterpret_casttra un std::vector<X>e std::pmr::vector<X>, se è quello che stai chiedendo.
davmac

4
Per casi semplici in cui la risorsa di memoria non dipende da una variabile di runtime, un buon compilatore devirtualizza e ti ritroverai con un allocatore polimorfico senza costi aggiuntivi (tranne per la memorizzazione del puntatore che non è davvero un problema). Ho pensato che valesse la pena menzionarlo.
DeiDei

1
@ Yakk-AdamNevraumont "un std::pmr::contenitore non è ancora compatibile con il std::contenitore equivalente che utilizza l'allocatore predefinito" . Non esiste nemmeno un operatore di assegnazione definito dall'uno all'altro. In caso di dubbio, provalo: godbolt.org/z/Q5BKev (il codice non è esattamente come sopra perché gcc / clang ha le classi di allocazione polimorfica in uno spazio dei nomi "sperimentale").
davmac

1
@davmac Ah, quindi non c'è un template<class OtherA, std::enable_if< A can be constructed from OtherA > vector( vector<T, OtherA>&& )costruttore. Non ero sicuro e non sapevo dove trovare un compilatore con pmr conforme a TS.
Yakk - Adam Nevraumont

33

polymorphic_allocator è a un allocatore personalizzato come std::function è per una chiamata di funzione diretta.

Ti consente semplicemente di utilizzare un allocatore con il tuo contenitore senza dover decidere, al momento della dichiarazione, quale. Quindi, se hai una situazione in cui più di un allocatore sarebbe appropriato, puoi usarepolymorphic_allocator .

Forse vuoi nascondere quale allocatore viene utilizzato per semplificare la tua interfaccia, o forse vuoi essere in grado di scambiarlo per diversi casi di runtime.

Per prima cosa hai bisogno di codice che necessita di un allocatore, quindi devi voler essere in grado di scambiare quale è usato, prima di considerare pmr vector.


7

Uno svantaggio degli allocatori polimorfici è che polymorphic_allocator<T>::pointerè sempre giusto T*. Ciò significa che non puoi usarli con puntatori fantasiosi . Se vuoi fare qualcosa come inserire gli elementi di a vectornella memoria condivisa e accedervi tramite boost::interprocess::offset_ptrs , devi usare un vecchio allocatore non polimorfico.

Pertanto, sebbene gli allocatori polimorfici consentano di variare il comportamento di allocazione senza modificare il tipo statico di un contenitore, limitano il significato di un'allocazione .


2
Questo è un punto chiave e un grande peccato. Il documento di Arthur O'Dwyer Verso i puntatori fantasia significativi esplora il territorio, così come il suo libro "Mastering the c ++ 17 STL"
visto il

puoi dare un caso d'uso nel mondo reale dell'utilizzo di allocatori polimorfici?
darune
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.