Posso assumere che gli allocatori non tengono direttamente il proprio pool di memoria (e può quindi essere copiato)?

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

  •  13-12-2019
  •  | 
  •  

Domanda

Sto scrivendo un contenitore e vorrei consentire all'utente di utilizzare allocatori personalizzati, ma non posso dire se dovrei passare gli allocatori in giro per riferimento o per valore.

è garantito (o almeno, un'ipotesi ragionevole da fare) che un oggetto di allocatore non contiene direttamente il proprio pool di memoria, e quindi sarebbe OK copiare un allocatore e aspettarsi la memoriaPiscine degli allocatori per essere compatibili con cross-compatibili?Oppure ho sempre bisogno di passare gli allocatori per riferimento?

(Ho scoperto che passare per riferimento per danneggiare le prestazioni di un fattore> 2 perché il compilatore inizia a preoccuparti di aliasing, quindi fa se o meno contare su questa ipotesi.)

È stato utile?

Soluzione

in C ++ 11 Sezione 17.6.3.5 Requisiti di allocatore [allocator.requirements] Specifica i requisiti per gli allocatori conformi. Tra i requisiti sono:

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 si copia un allocatore, le due copie sono necessarie per essere in grado di eliminare i puntatori dell'altro.

concepibilmente si potrebbe mettere i buffer interni in allocatori, ma le copie dovrebbero mantenere un elenco dei buffer degli altri. O forse un allocatore potrebbe avere un invariante che deallocazione è sempre un no-op perché il puntatore proviene sempre da un buffer interno (da proprio, o da qualche altra copia).

Ma qualunque sia lo schema, copie deve essere "compatibile con croce".

Aggiornamento

Ecco un allocatore conforme a C ++ 11 che fa "ottimizzazione corta corta". Per renderlo C ++ 11 conforme, ho dovuto mettere il tampone "interno" esterno all'assegnatore in modo che le copie siano uguali:

#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);
}
.

Potrebbe essere usato come questo:

#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;
}
.

Tutte le allocazioni per il problema di cui sopra sono tratte dal arena locale che è di dimensioni da 1 KB. Dovresti essere in grado di superare questo allocatore in giro per valore o per riferimento.

Altri suggerimenti

Il vecchio standard C ++ rende i requisiti per un allocatore conforme a standard: questi requisiti includono che se si dispone di Alloc<T> a, b, quindi a == b e è possibile utilizzare b per deallocare le cose che sono state assegnate con a. Gli allocatori sono fondamentalmente apolide .


.

In C ++ 11, la situazione è stata molto più coinvolta, poiché ora è il supporto per stateful allocatori. Mentre si copia e sposta oggetti intorno, ci sono regole specifiche se un contenitore può essere copiato o spostato da un altro contenitore se gli alianti differiscono e come gli allocatori vengono copiati o spostati.

Solo per rispondere prima della tua domanda: No, puoi sicuramente non supponi che abbia senso copiare il tuo allocatore e il tuo allocatore potrebbe non essere nemmeno copiabile.

Ecco 23.2.1 / 7 su questo argomento:

.

Salvo diversa indicazione, tutti i contenitori definiti in questa clausola ottengono memoria utilizzando un allocatore (vedere 17.6.3.5). Copia costruttori per questi tipi di container Ottieni un allocatore chiamando allocator_traits<allocator_-type>::select_on_container_copy_construction sui loro primi parametri. Sposta i costruttori Ottenere un allocatore per movimento della mossa dall'allocatore appartenente al contenitore che viene spostato. Tale spostamento della costruzione dell'Assegnatore non deve uscire tramite un'eccezione. Tutti gli altri costruttori per questi tipi di container prendono un argomento Allocator& (17.6.3.5), un allocatore il cui tipo di valore è lo stesso del tipo di valore del contenitore. [Nota: se un'invocazione di un costruttore utilizza il valore predefinito di un argomento di allocatore opzionale, il tipo di allocatore deve supportare l'inizializzazione del valore. -End Nota] Una copia di questo allocatore viene utilizzata per qualsiasi allocazione della memoria eseguita, da questi costruttori e da tutte le funzioni dei membri, durante la durata di ciascun oggetto container o fino a quando non viene sostituito l'allocatore. L'allocatore può essere sostituito solo tramite assegnazione o scambiare(). La sostituzione dell allocatore viene eseguita mediante assegnazione della copia, sposta l'assegnazione o lo scambio dell'allocatore solo se allocator_traits<allocator_type>::propagate_on_container_copy_assignment::value, allocator_traits<allocator_type>::propagate_on_container_move_assignment::value o allocator_traits<allocator_type>::propagate_on_container_swap::value è vero nell'attuazione dell'operazione del contenitore corrispondente. Il comportamento di una chiamata alla funzione di swap di un contenitore non è definito a meno che gli oggetti vengano scambiati non hanno confronto gli allocatori che confrontano uguali o allocator_traits<allocator_type>::propagate_on_container_swap::value è vero. In tutti i tipi di contenitori definiti in questa clausola, il membro get_allocator() restituisce una copia dell'allocatore utilizzato per costruire il contenitore o, se tale allocatore è stato sostituito, una copia della sostituzione più recente.

Vedi anche la documentazione di std::allocator_traits per una sinossi.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top