¿Puedo asumir que los asignadores no retienen su grupo de memoria directamente (y, por lo tanto, pueden copiarse)?

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

  •  13-12-2019
  •  | 
  •  

Pregunta

Estoy escribiendo un contenedor y me gustaría permitir que el usuario use asignadores personalizados, pero no sé si debo pasar asignadores por referencia o por valor.

¿Está garantizado (o al menos es una suposición razonable) que un objeto asignador no contener su grupo de memoria directamente y, por lo tanto, ¿estaría bien copiar un asignador y esperar que los grupos de memoria de los asignadores sean compatibles entre sí?¿O siempre necesito pasar los asignadores por referencia?

(He descubierto que pasar por referencia perjudica el rendimiento en un factor de > 2 porque el compilador comienza a preocuparse por el alias, por lo que determina si puedo o no confiar en esta suposición).

¿Fue útil?

Solución

En C++11, la sección 17.6.3.5 Requisitos del asignador [allocator.requirements] especifica los requisitos para los asignadores conformes.Entre los requisitos están:

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

Es decir.cuando copia un asignador, se requieren las dos copias para poder eliminar los punteros de cada uno.

Posiblemente se podrían colocar buffers internos en asignadores, pero las copias tendrían que mantener una lista de los buffers de otros.O tal vez un asignador podría tener una invariante de que la desasignación siempre es no operativa porque el puntero siempre proviene de un búfer interno (ya sea del suyo propio o de alguna otra copia).

Pero cualquiera que sea el esquema, las copias deben ser "compatibles entre sí".

Actualizar

Aquí hay un asignador compatible con C++11 que realiza la "optimización de cadenas cortas".Para que sea compatible con C++11, tuve que colocar el búfer "interno" externo al asignador para que las copias sean iguales:

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

Podría usarse así:

#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 las asignaciones para el problema anterior provienen del gobierno local. arena que tiene un tamaño de 1 Kb.Debería poder pasar este asignador por valor o por referencia.

Otros consejos

El antiguo estándar C++ establece requisitos para un asignador compatible con el estándar:Estos requisitos incluyen que si tiene Alloc<T> a, b, entonces a == b, y puedes usar b desasignar cosas que fueron asignadas con a.Los asignadores son fundamentalmente apátrida.


En C++ 11, la situación se volvió mucho más complicada, ya que ahora hay soporte para con estado asignadores.A medida que copia y mueve objetos, existen reglas específicas sobre si un contenedor se puede copiar o mover desde otro contenedor si los asignadores difieren, y cómo se copian o mueven los asignadores.

Sólo para responder a tu pregunta primero:No, definitivamente puedes no Suponga que tiene sentido copiar su asignador y es posible que su asignador ni siquiera se pueda copiar.

Aquí está el 23.2.1/7 sobre este tema:

A menos que se especifique lo contrario, todos los contenedores definidos en esta cláusula obtienen memoria utilizando un asignador (ver 17.6.3.5).Los constructores de copia para estos tipos de contenedores obtienen un asignador llamando allocator_traits<allocator_-type>::select_on_container_copy_construction en sus primeros parámetros.Los constructores de movimiento obtienen un asignador mediante la construcción de movimiento del asignador que pertenece al contenedor que se está moviendo.Dicha construcción de movimiento del asignador no saldrá a través de una excepción.Todos los demás constructores para estos tipos de contenedores toman un Allocator& argumento (17.6.3.5), un asignador cuyo tipo de valor es el mismo que el tipo de valor del contenedor.[Nota:Si una invocación de un constructor utiliza el valor predeterminado de un argumento de asignador opcional, entonces el tipo Allocator debe admitir la inicialización de valores.—Nota final] Se utiliza una copia de este asignador para cualquier asignación de memoria realizada por estos constructores y por todas las funciones miembro, durante la vida útil de cada objeto contenedor o hasta que se reemplace el asignador.El asignador puede reemplazarse solo mediante asignación o swap ().El reemplazo del asignador se realiza mediante asignación de copia, asignación de movimiento o intercambio del asignador solo si 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 es verdadero dentro de la implementación de la operación de contenedor correspondiente.El comportamiento de una llamada a la función de intercambio de un contenedor no está definido a menos que los objetos que se intercambian tengan asignadores que se comparen iguales o allocator_traits<allocator_type>::propagate_on_container_swap::value es verdad.En todos los tipos de contenedores definidos en esta Cláusula, el miembro get_allocator() devuelve una copia del asignador utilizado para construir el contenedor o, si ese asignador ha sido reemplazado, una copia del reemplazo más reciente.

Véase también la documentación de std::allocator_traits para una sinopsis.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top