2012-03-18 1268 views
5

¿Tiene sentido inicializar la memoria con std::uninitialized_fill() en una biblioteca cuando se ha utilizado un asignador pasado como argumento por el usuario para obtener la memoria en sí? Pregunto esto porque se supone que un asignador debe haber proporcionado su propio método construct() (que no sea el método allocate()), cuya implementación puede diferir del estándar, por lo que probablemente std::uninitialized_fill() no siempre sea apropiado en todos los casos.¿Tiene sentido utilizar std :: uninitialized_fill() con cualquier asignador?

Para ser precisos, mis dudas provienen del libro de C++ escrito por Stroustrup (apéndice E "Seguridad de excepción de la biblioteca estándar", sección E. 3.1), en el que el autor da una posible implementación de template<class T, class A> vector<T,A>::vector(size_type n, const T& val, const A& a): el asignador se usa para obtener la memoria para el vector, luego se usa std::uninitialized_fill() para inicializar la memoria obtenida.

También proporciona la implementación de std::uninitialized_fill(), que internamente utiliza la colocación estándar nueva para inicializar la memoria, pero ya no hay evidencia del método construct() del asignador pasado como argumento para el constructor vectorial.

+1

Qué es 'std :: initialize_fill() '? ¿Quiere decir 'std :: uninitialized_fill()'? La función 'initialize_fill()' no aparece en ningún lugar en el estándar C++, a diferencia de 'uninitialized_fill()'. –

+0

@Insilico lo siento, fue un error tipográfico. gracias. Arreglé la pregunta. – Martin

+2

Esto parece estar relacionado con mi pregunta: http://stackoverflow.com/questions/9727556/is-uninitialized-copy-fillin-first-in-last-for-dest-aa-an-oversight-in-th, lo hago piense que en realidad probablemente sea seguro usar 'uninitialized_fill' ordinario –

Respuesta

2

he comprobado la aplicación de GCC vector, se lee:

vector(size_type __n, const value_type& __value, 
     const allocator_type& __a = allocator_type()) 
    : _Base(__n, __a) 
    { _M_fill_initialize(__n, __value); } 

    ... 

    _M_fill_initialize(size_type __n, const value_type& __value) 
    { 
    this->_M_impl._M_finish = 
     std::__uninitialized_fill_n_a(this->_M_impl._M_start, __n, __value, 
            _M_get_Tp_allocator()); 
    } 

Por lo tanto, parece que la información del asignador no desaparece. Sin embargo, el código de uninitialized_fill_n_a es extraño, tiene dos sobrecargas (ver a continuación), una para el asignador genérico y otra para std::allocator.

Las llamadas genéricas, construct y las especiales para std::allocator simplemente llaman a std::uninitialized_fill().

Por lo tanto, mi conclusión es la siguiente,

1) todos std::allocator 's construct s tienen el mismo efecto que la colocación de nuevo y

2) que no puede ser asumida por los asignadores de genéricos, o al menos GCC std::vector no supone eso. (Según @Michael Burr puedes asumir esto en C++ 03, lamentablemente no menciona C++ 11).

por lo tanto, use construct (es decir, std::allocator_traits<Alloc>::construct(alloc, pointer, value)).

Mi opinión personal (después de investigar esto), es que

i) std::uninitialized_fill es defectuoso, en el que debe tomar un último argumento opcional (u otra sobrecarga) con el asignador,

ii) una solución alternativa, dentro de los detalles de su implementación debe tener una función .uninitialized_fill(first, last, value, alloc) que hace el trabajo, incluido el manejo de excepciones (observe a continuación cómo se desenrollan las construcciones con destruye al fallar). .

iii) actual std::unitialized_fill es bastante inútil cuando se tiene información sobre el asignador (y, básicamente, tiene que volver a implementar es)


ahora el código de referencia más arriba:

template<typename _ForwardIterator, typename _Size, typename _Tp, 
      typename _Allocator> 
    _ForwardIterator 
    __uninitialized_fill_n_a(_ForwardIterator __first, _Size __n, 
          const _Tp& __x, _Allocator& __alloc) 
    { 
     _ForwardIterator __cur = __first; 
     __try 
     { 
      typedef __gnu_cxx::__alloc_traits<_Allocator> __traits; 
      for (; __n > 0; --__n, ++__cur) 
      __traits::construct(__alloc, std::__addressof(*__cur), __x); 
      return __cur; 
     } 
     __catch(...) 
     { 
      std::_Destroy(__first, __cur, __alloc); 
      __throw_exception_again; 
     } 
    } 

    template<typename _ForwardIterator, typename _Size, typename _Tp, 
      typename _Tp2> 
    inline _ForwardIterator 
    __uninitialized_fill_n_a(_ForwardIterator __first, _Size __n, 
          const _Tp& __x, allocator<_Tp2>&) 
    { return std::uninitialized_fill_n(__first, __n, __x); } 
0

La implementación de cualquier función del asignador no predeterminado construct() es necesaria para tener el efecto de una ubicación nueva (según la Tabla 32 - Requisitos de asignación en C++ 03).

Por lo tanto, usar std::uninitialized_fill() (que se define como realizar una colocación nueva en cada elemento del rango) debería estar bien, incluso si se usa un asignador personalizado.

+2

Creo que está preguntando si está bien * reemplazar * la llamada a 'construir' con' uninitialized_fill', que es diferente. 'construct' puede tener efectos adicionales más allá del requisito. – Potatoswatter

1

Prefiero llamar al construct.

Existen varias razones para proporcionar un asignador personalizado, y una mejor política de asignación (uso de agrupaciones/almacenamiento de pila) no es la única.

También he visto (y he incursionado con) asignadores de depuración para ayudar a rastrear el uso de asignadores incorrectos en contenedores personalizados. También es posible utilizar asignadores para rastrear el uso de memoria, recopilar estadísticas, etc. ...

Como tal, construct podría (o no) tener efectos adicionales más allá de la construcción del objeto, y por lo tanto no es necesariamente equivalente a colocación simple new.

En mi asignador de depuración, llamando destroy sin haber llamado construct en esa dirección antes era un error (e igualmente la recuperación de la memoria sin haber llamado destroy), por lo que al menos yo recomendaría siendo consistente y emparejamiento construct con destroy y colocación new con una llamada de destructor explícita.

+0

'destroy', not' destruct' (ha sido mordido por este varias veces, a diferencia del francés, es asimétrico :). –

+0

@AlexandreC .: gracias, eso es lo que recibo por no verificar ... –

Cuestiones relacionadas