2011-08-28 8 views
9

Estoy interesado en construir un contenedor uninitialized_vector, que será semánticamente idéntico a std::vector con la advertencia de que los nuevos elementos que de otro modo se crearían con un constructor sin argumentos se crearán en su lugar sin inicialización Principalmente estoy interesado en evitar inicializar POD a 0. Hasta donde puedo decir, no hay forma de lograr esto combinando std::vector con un tipo especial de asignador. Evitar la construcción predeterminada de elementos en contenedores estándar

me gustaría construir mi contenedor en la misma línea que std::stack, que se adapta un recipiente proporcionado por el usuario (en mi caso, std::vector). En otras palabras, me gustaría evitar volver a implementar la totalidad de std::vector y en su lugar proporcionar una "fachada" a su alrededor.

¿Existe una manera simple de controlar la construcción predeterminada desde el "exterior" de std::vector?


Aquí está la solución llegué a, que se inspiró la respuesta de kerrek:

#include <iostream> 
#include <vector> 
#include <memory> 
#include <algorithm> 
#include <cassert> 

// uninitialized_allocator adapts a given base allocator 
// uninitialized_allocator's behavior is equivalent to the base 
// except for its no-argument construct function, which is a no-op 
template<typename T, typename BaseAllocator = std::allocator<T>> 
    struct uninitialized_allocator 
    : BaseAllocator::template rebind<T>::other 
{ 
    typedef typename BaseAllocator::template rebind<T>::other super_t; 

    template<typename U> 
    struct rebind 
    { 
    typedef uninitialized_allocator<U, BaseAllocator> other; 
    }; 

    // XXX for testing purposes 
    typename super_t::pointer allocate(typename super_t::size_type n) 
    { 
    auto result = super_t::allocate(n); 

    // fill result with 13 so we can check afterwards that 
    // the result was not default-constructed 
    std::fill(result, result + n, 13); 
    return result; 
    } 

    // catch default-construction 
    void construct(T *p) 
    { 
    // no-op 
    } 

    // forward everything else with at least one argument to the base 
    template<typename Arg1, typename... Args> 
    void construct(T* p, Arg1 &&arg1, Args&&... args) 
    { 
    super_t::construct(p, std::forward<Arg1>(arg1), std::forward<Args>(args)...); 
    } 
}; 

namespace std 
{ 

// XXX specialize allocator_traits 
//  this shouldn't be necessary, but clang++ 2.7 + libc++ has trouble 
//  recognizing that uninitialized_allocator<T> has a well-formed 
//  construct function 
template<typename T> 
    struct allocator_traits<uninitialized_allocator<T> > 
    : std::allocator_traits<std::allocator<T>> 
{ 
    typedef uninitialized_allocator<T> allocator_type; 

    // for testing purposes, forward allocate through 
    static typename allocator_type::pointer allocate(allocator_type &a, typename allocator_type::size_type n) 
    { 
    return a.allocate(n); 
    } 

    template<typename... Args> 
    static void construct(allocator_type &a, T* ptr, Args&&... args) 
    { 
    a.construct(ptr, std::forward<Args>(args)...); 
    }; 
}; 

} 

// uninitialized_vector is implemented by adapting an allocator and 
// inheriting from std::vector 
// a template alias would be another possiblity 

// XXX does not compile with clang++ 2.9 
//template<typename T, typename BaseAllocator> 
//using uninitialized_vector = std::vector<T, uninitialized_allocator<T,BaseAllocator>>; 

template<typename T, typename BaseAllocator = std::allocator<T>> 
    struct uninitialized_vector 
    : std::vector<T, uninitialized_allocator<T,BaseAllocator>> 
{}; 

int main() 
{ 
    uninitialized_vector<int> vec; 
    vec.resize(10); 

    // everything should be 13 
    assert(std::count(vec.begin(), vec.end(), 13) == vec.size()); 

    // copy construction should be preserved 
    vec.push_back(7); 
    assert(7 == vec.back()); 

    return 0; 
} 

Esta solución funcionará dependiendo de cómo de cerca compilador & std::vector aplicación de STL de un proveedor en particular se ajusta a C++ 11.

+0

¿Puedes simplemente definir un constructor predeterminado que no haga nada? Apuesto a que el compilador puede incluir u omitirlo esp con la optimización activada. –

+0

@Doug T.- Eso puede resolver el caso del constructor predeterminado, pero las cosas interesantes parecen suceder en '' 'resize''' - no está claro cómo llamar a funciones como' '' resize''' sin invocar constructores. –

+1

@Doug: Tiene la advertencia de que ya no eres POD con un constructor definido por el usuario ... –

Respuesta

5

Creo que el problema se reduce al tipo de inicialización que el contenedor lleva a cabo en los elementos. Compare:

T * p1 = new T; // default-initalization 
T * p2 = new T(); // value-initialization 

El problema con los contenedores estándar es que toman el argumento predeterminado que se inicializa el valor, como en resize(size_t, T = T()). Esto significa que no hay una forma elegante de evitar la inicialización de valor o la copia. (De forma similar para el constructor.)

Incluso el uso de los asignadores estándar no funciona, porque su función central construct() toma un argumento que se inicializa con el valor. Lo que se necesita es un lugar construct() que utiliza por defecto-inicialización:

template <typename T> 
void definit_construct(void * addr) 
{ 
    new (addr) T; // default-initialization 
} 

Tal cosa no sería un factor de imputación estándar conforme más, pero se podría construir su propio contenedor en torno a esa idea.

+0

Gracias por esta respuesta, pero no entiendo tu comentario sobre '' 'construct()' '' tomando un argumento que se inicializa con el valor. ¿Por qué no puede ser un no-op? –

+0

@Jared: Solo me refiero a la forma en que se usan los asignadores estándar en contenedores estándar; su función 'construct()' suele ser algo así como 'construct (void *, const T &)', por lo que hay al menos una copia durante la construcción pero me imagino que esta función se llamaría de todos modos con un objeto inicializado de valor, como 'constructo (p, T())', por lo que no se puede escapar a la inicialización del valor dentro de los contenedores estándar. (Incluso los asignadores de emplazamiento de C++ 11 no pueden ayudarte porque no puedes "mover" algo más de una vez ...) –

+0

Así que, básicamente, estoy diciendo que podrías armar un contenedor que se comporte muy parecido a 'vector' , pero en lugar de llamar al asignador estándar, simplemente harías que dijera 'new (addr) T;' en lugar de llamar 'construct()'. Puede mantener los otros aspectos del asignador (es decir, asignación de memoria). –

1

No creo que esto sea posible envolviendo un vector (que funciona con todos los tipos), a menos que cambie el tamaño del vector en cada operación de agregar y eliminar.

Si pudiera dejar de envolver contenedores STL, puede hacer esto manteniendo una matriz de char en el montón y utilizando la ubicación new para cada uno de los objetos que desea construir. De esta forma, podría controlar exactamente cuándo se llamaron los constructores y los destructores de los objetos, uno por uno.

6

En lugar de utilizar un envoltorio alrededor del contenedor, considere el uso de un envoltorio sobre el tipo de elemento:

template <typename T> 
struct uninitialized 
{ 
    uninitialized() { } 
    T value; 
}; 
+1

¿Aún no se define 'valor'? –

+0

@Seth: si 'T' es POD,' value' no se inicializará. –

+0

Ah, no me di cuenta de que estaba usando solo tipos de POD. –

Cuestiones relacionadas