polymorphic_allocator: quando e por que devo usá-lo?

121

Aqui está a documentação sobre cppreference , aqui está o esboço de trabalho.

Devo admitir que não entendi qual é o verdadeiro propósito polymorphic_allocatore quando / por que / como devo usá-lo.
Como exemplo, o pmr::vectortem a seguinte assinatura:

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

O que a polymorphic_allocatoroferta? O que a std::pmr::vectoroferta também oferece em relação aos antiquados std::vector? O que posso fazer agora que não fui capaz de fazer até agora?
Qual é o propósito real desse alocador e quando devo usá-lo de fato?

skypjack
fonte
1
Eles tentam superar alguns problemas allocator<T>inerentemente. Portanto, você verá valor nisso se usar alocadores com frequência.
edmz
2
Papel relevante .
edmz

Respostas:

102

Citação de escolha de cppreference:

Este polimorfismo de tempo de execução permite que os objetos que usam polymorphic_allocator se comportem como se usassem diferentes tipos de alocadores em tempo de execução, apesar do tipo de alocador estático idêntico

O problema com os alocadores "regulares" é que eles mudam o tipo do contêiner. Se você quiser um vectorcom um alocador específico, pode usar o Allocatorparâmetro do modelo:

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

O problema agora é que esse vetor não é do mesmo tipo que um vetor com um alocador diferente. Você não pode passá-lo para uma função que requer um vetor alocador padrão, por exemplo, ou atribuir dois vetores com um tipo de alocador diferente para a mesma variável / ponteiro, por exemplo:

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

Um alocador polimórfico é um único tipo de alocador com um membro que pode definir o comportamento do alocador por meio de despacho dinâmico em vez de por meio do mecanismo de modelo. Isso permite que você tenha contêineres que usam alocação específica e personalizada, mas que ainda são de um tipo comum.

A personalização do comportamento do alocador é feita dando ao alocador um 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

O principal problema remanescente, a meu ver, é que um std::pmr::contêiner ainda não é compatível com o std::contêiner equivalente que usa o alocador padrão. Você precisa tomar algumas decisões no momento de projetar uma interface que funcione com um contêiner:

  • é provável que o contêiner passado possa exigir alocação personalizada?
  • em caso afirmativo, devo adicionar um parâmetro de modelo (para permitir alocadores arbitrários) ou devo obrigar o uso de um alocador polimórfico?

Uma solução de modelo permite qualquer alocador, incluindo um alocador polimórfico, mas tem outras desvantagens (tamanho do código gerado, tempo de compilação, o código deve ser exposto no arquivo de cabeçalho, potencial para mais "contaminação de tipo" que continua empurrando o problema para fora). Uma solução de alocador polimórfico, por outro lado, determina que um alocador polimórfico deve ser usado. Isso impede o uso de std::contêineres que usam o alocador padrão e pode ter implicações para a interface com o código legado.

Comparado a um alocador regular, um alocador polimórfico tem alguns custos menores, como a sobrecarga de armazenamento do ponteiro memory_resource (que é provavelmente insignificante) e o custo de despacho de função virtual para alocações. O principal problema, realmente, é provavelmente a falta de compatibilidade com o código legado que não usa alocadores polimórficos.

Davmac
fonte
2
Portanto, é std::pmr::provável que o layout binário para classes seja diferente?
Euri Pinhollow
12
@EuriPinhollow você não pode reinterpret_castentre um std::vector<X>e std::pmr::vector<X>, se é isso que você está perguntando.
Davmac
4
Para casos simples em que o recurso de memória não depende de uma variável de tempo de execução, um bom compilador desvirtualiza e você acaba com um alocador polimórfico sem nenhum custo extra (exceto para armazenar o ponteiro que realmente não é um problema). Achei que valia a pena mencionar.
DeiDei
1
@ Yakk-AdamNevraumont "um std::pmr::contêiner ainda não é compatível com o std::contêiner equivalente usando o alocador padrão" . Também não há operador de atribuição definido de um para o outro. Em caso de dúvida, experimente: godbolt.org/z/Q5BKev (o código não é exatamente como o anterior porque gcc / clang tem as classes de alocação polimórfica em um namespace "experimental").
Davmac
1
@davmac Ah, então não há um template<class OtherA, std::enable_if< A can be constructed from OtherA > vector( vector<T, OtherA>&& )construtor. Eu não tinha certeza e não sabia onde encontrar um compilador que tivesse PMR compatível com TS.
Yakk - Adam Nevraumont
33

polymorphic_allocatoré para um alocador personalizado assim como std::functionpara uma chamada de função direta.

Ele simplesmente permite que você use um alocador com seu contêiner sem ter que decidir, no ponto de declaração, qual deles. Portanto, se você tiver uma situação em que mais de um alocador seja apropriado, você pode usar polymorphic_allocator.

Talvez você queira ocultar qual alocador é usado para simplificar sua interface, ou talvez queira trocá-lo por diferentes casos de tempo de execução.

Primeiro você precisa de um código que precisa de um alocador, então você precisa ser capaz de trocar qual deles é usado, antes de considerar o vetor pmr.

Yakk - Adam Nevraumont
fonte
7

Uma desvantagem dos alocadores polimórficos é que eles polymorphic_allocator<T>::pointersão sempre justos T*. Isso significa que você não pode usá-los com dicas sofisticadas . Se você quiser fazer algo como colocar elementos de a vectorna memória compartilhada e acessá-los por meio de boost::interprocess::offset_ptrs , você precisa usar um alocador não polimórfico antigo regular para isso.

Portanto, embora os alocadores polimórficos permitam variar a alocação comportamento da sem alterar o tipo estático de um contêiner, eles limitam o que é uma alocação .

Maxpm
fonte
2
Este é um ponto-chave e uma grande chatice. O artigo de Arthur O'Dwyer, Towards Scientific fancy pointers , explora o território, assim como seu livro "Mastering the c ++ 17 STL"
ver
você pode dar um caso de uso do mundo real usando o alocador polimórfico?
darune