Posso assumir usufruem de não segurar a sua memória de pool diretamente (e, portanto, pode ser copiada)?

StackOverflow https://stackoverflow.com//questions/11703643

  •  13-12-2019
  •  | 
  •  

Pergunta

Eu estou escrevendo um recipiente e gostaria de permitir que o usuário utilize o custom allocators, mas eu não posso dizer se eu deveria passar usufruem de volta, por referência ou por valor.

É garantido (ou, pelo menos, uma suposição razoável para o fazer) que um alocador de objeto não conter o seu pool de memória diretamente, e, portanto, seria OK para copiar uma função de alocação e esperar os pools de memória do allocators para ser compatível?Ou eu sempre precisa passar allocators por referência?

(Eu descobri que a passagem por referência prejudica o desempenho por um fator de > 2 porque o compilador começa a preocupar-se sobre alias, assim que faz um se quer ou não se pode confiar essa suposição.)

Foi útil?

Solução

Em C++11 seção 17.6.3.5 Alocador de requisitos [alocador.requisitos especifica os requisitos para conformidade allocators.Entre os requisitos estão:

X                    an Allocator class for type T
...
a, a1, a2            values of type X&
...
a1 == a2             bool          returns true only if storage
                                   allocated from each can be
                                   deallocated via the other.
                                   operator== shall be reflexive,
                                   symmetric, and transitive, and
                                   shall not exit via an exception.
...
X a1(a);                           Shall not exit via an exception.
                                   post: a1 == a

I. e.quando você copiar uma função de alocação, as duas cópias são necessárias para ser capaz de eliminar uns dos outros ponteiros.

Teoricamente poderia colocar os buffers internos para allocators, mas cópias teria de manter uma lista de outros buffers.Ou, talvez, uma função de alocação poderia ter um invariante que liberação é sempre um não-op porque o ponteiro sempre vem de um buffer interno (seja a partir do seu próprio, ou de alguma outra cópia).

Mas qualquer que seja o regime de cópias deve ser "compatível".

Atualização

Aqui é um C++11 conforme alocador de que o "curto otimização de seqüência de caracteres".Para torná-lo C++11 conformes, eu tinha que colocar o "interno" buffer externo para o alocador de modo a que as cópias são iguais:

#include <cstddef>

template <std::size_t N>
class arena
{
    static const std::size_t alignment = 16;
    alignas(alignment) char buf_[N];
    char* ptr_;

    std::size_t 
    align_up(std::size_t n) {return n + (alignment-1) & ~(alignment-1);}

public:
    arena() : ptr_(buf_) {}
    arena(const arena&) = delete;
    arena& operator=(const arena&) = delete;

    char* allocate(std::size_t n)
    {
        n = align_up(n);
        if (buf_ + N - ptr_ >= n)
        {
            char* r = ptr_;
            ptr_ += n;
            return r;
        }
        return static_cast<char*>(::operator new(n));
    }
    void deallocate(char* p, std::size_t n)
    {
        n = align_up(n);
        if (buf_ <= p && p < buf_ + N)
        {
            if (p + n == ptr_)
                ptr_ = p;
        }
        else
            ::operator delete(p);
    }
};

template <class T, std::size_t N>
class stack_allocator
{
    arena<N>& a_;
public:
    typedef T value_type;

public:
    template <class U> struct rebind {typedef stack_allocator<U, N> other;};

    explicit stack_allocator(arena<N>& a) : a_(a) {}
    template <class U>
        stack_allocator(const stack_allocator<U, N>& a)
            : a_(a.a_) {}
    stack_allocator(const stack_allocator&) = default;
    stack_allocator& operator=(const stack_allocator&) = delete;

    T* allocate(std::size_t n)
    {
        return reinterpret_cast<T*>(a_.allocate(n*sizeof(T)));
    }
    void deallocate(T* p, std::size_t n)
    {
        a_.deallocate(reinterpret_cast<char*>(p), n*sizeof(T));
    }

    template <class T1, std::size_t N1, class U, std::size_t M>
    friend
    bool
    operator==(const stack_allocator<T1, N1>& x, const stack_allocator<U, M>& y);

    template <class U, std::size_t M> friend class stack_allocator;
};

template <class T, std::size_t N, class U, std::size_t M>
bool
operator==(const stack_allocator<T, N>& x, const stack_allocator<U, M>& y)
{
    return N == M && &x.a_ == &y.a_;
}

template <class T, std::size_t N, class U, std::size_t M>
bool
operator!=(const stack_allocator<T, N>& x, const stack_allocator<U, M>& y)
{
    return !(x == y);
}

Ela pode ser usada como esta:

#include <vector>

template <class T, std::size_t N> using A = stack_allocator<T, N>;
template <class T, std::size_t N> using Vector = std::vector<T, stack_allocator<T, N>>;

int main()
{
    const std::size_t N = 1024;
    arena<N> a;
    Vector<int, N> v{A<int, N>(a)};
    v.reserve(100);
    for (int i = 0; i < 100; ++i)
        v.push_back(i);
    Vector<int, N> v2 = std::move(v);
    v = v2;
}

Todas as alocações para o problema acima são desenhadas a partir do local arena o que é de 1 Kb de tamanho.Você deve ser capaz de passar esta atribuição ao redor por valor ou por referência.

Outras dicas

O velho padrão de C++ faz requisitos para um padrão compatível com atribuição de:Estes requisitos incluem a de que, se você tem Alloc<T> a, b, e , em seguida, a == b, e você pode usar b para desalocar as coisas que foram alocados com a.Allocators são fundamentalmente sem monitoração de estado.


Em C++11, a situação ficou muito mais envolvidos, como agora há suporte para com monitoração de estado allocators.Como copiar e mover objetos, existem regras específicas que, se um recipiente podem ser copiados ou movidos de um outro recipiente, se o allocators diferentes, e como o allocators copiado ou movido.

Apenas a resposta a sua primeira pergunta:Não, você pode definitivamente não suponha que faz sentido para copiar o alocador de volta, e a sua atribuição não pode mesmo ser copiáveis.

Aqui é 23.2.1/7 sobre este assunto:

A menos que especificado em contrário, todos os recipientes definidos nesta cláusula obter memória usando uma função de alocação (ver 17.6.3.5).Os construtores de cópia para estes tipos de recipiente obter um alocador chamando allocator_traits<allocator_-type>::select_on_container_copy_construction em sua primeira parâmetros.Os construtores de movimentação obter um alocador por mover construção do alocador pertencentes ao recipiente que está sendo movida.Tal movimento de construção do alocador de não sair através de uma exceção.Todos os outros construtores para estes tipos de recipiente tomar um Allocator& argumento (17.6.3.5), um alocador de cujo tipo de valor é o mesmo que o recipiente do tipo de valor.[Nota:Se uma chamada de um construtor usa o valor padrão de um opcional atribuição de argumento e, em seguida, a Atribuição de tipo deve suportar a inicialização do valor.—nota final] Uma cópia desta atribuição é utilizado para qualquer alocação de memória executadas por esses construtores e por todas as funções de membro, durante o tempo de vida de cada objeto de recipiente ou até que a atribuição é substituído.A atribuição pode ser substituído somente através de cessão ou swap().Alocador de substituição é realizada por cópia atribuição, atribuição de movimentação, ou a troca do alocador somente se allocator_traits<allocator_type>::propagate_on_container_copy_assignment::value, allocator_traits<allocator_type>::propagate_on_container_move_assignment::value, ou allocator_traits<allocator_type>::propagate_on_container_swap::value é verdade no âmbito da implementação do correspondente recipiente de operação.O comportamento de uma chamada para um recipiente de permuta função é definida, a menos que os objetos que estão sendo trocados ter allocators que comparar igual ou allocator_traits<allocator_type>::propagate_on_container_swap::value é verdade.Em todos os tipos de recipiente definido nesta Cláusula, o membro get_allocator() retorna uma cópia do alocador usado para construir o recipiente ou, se essa atribuição foi substituído, uma cópia do mais recente substituição.

Consulte também a documentação de std::allocator_traits para uma sinopse.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top