2010-03-12 16 views
124

¿Cuál es la forma más económica de inicializar un std::vector desde una matriz estilo C?¿Cómo inicializar std :: vector desde C-style array?

Ejemplo: En la clase siguiente, tengo una vector, pero debido a las restricciones externas, se pasará en los datos como una matriz de estilo C:

class Foo { 
    std::vector<double> w_; 
public: 
    void set_data(double* w, int len){ 
    // how to cheaply initialize the std::vector? 
} 

Obviamente, puedo llamar w_.resize() y luego bucle sobre los elementos, o llame al std::copy(). ¿Hay algún método mejor?

+7

La clave del problema es que no hay forma de que el vector sepa si se utilizó el mismo asignador para crear su matriz estilo C. Como tal, el vector debe asignar memoria usando su propio asignador. De lo contrario, podría simplemente cambiar la matriz subyacente y reemplazarla con su matriz. – Void

Respuesta

180

No se olvide que se puede tratar como punteros iteradores:

w_.assign(w, w + len); 
+0

¿Existe una diferencia de rendimiento entre 'std :: copy (w, w + len, w_.begin())' y su solución 'assign'? – Frank

+0

Oh, supongo que la diferencia es que 'std :: copy' no cambiará el tamaño de la matriz. – Frank

+0

No sé si * assign * es lo suficientemente inteligente como para calcular cuánto * w * es distante de * w + len * (un iterador genérico AFAIK no proporciona una forma rápida de hacerlo), por lo que puede mejorar un poco las actuaciones poniendo un w_.reserve (len) antes de esa declaración; en el peor de los casos, no ganas nada. De esta forma, debería tener más o menos el rendimiento de resize + copy. –

31

se utiliza la palabra inicializar por lo que no está claro si se trata de la asignación de una sola vez o puede ocurrir varias veces.

Si sólo necesita una inicialización de una vez, se puede poner en el constructor y el uso de los dos iterador vector constructor:

Foo::Foo(double* w, int len) : w_(w, w + len) { } 

De lo contrario usar asignar como sugerido anteriormente:

void set_data(double* w, int len) 
{ 
    w_.assign(w, w + len); 
} 
+0

En mi caso, la tarea sucederá repetidamente. – Frank

1

std::vector<double>::assign es el camino a seguir, porque es pequeño código. Pero, ¿cómo funciona, en realidad? ¿No cambia el tamaño y luego copia? En la implementación de MS de STL que estoy usando lo hace exactamente.

Me temo que no hay ninguna manera más rápida para implementar la (re) inicialización de su std::vector.

+0

¿Qué pasa si los datos se comparten entre el vector y una matriz? ¿Necesitamos copiar algo en este caso? – Vlad

8

Puede 'aprender' el tamaño de la matriz de forma automática:

template<typename T, size_t N> 
void set_data(const T (&w)[N]){ 
    w_.assign(w, w+N); 
} 

Con suerte, puede cambiar la interfaz a set_data que el anterior. Todavía acepta una matriz estilo C como primer argumento. Simplemente sucede tomarlo por referencia.


Cómo funciona

[Actualización: Ver here para una discusión más amplia sobre el aprendizaje del tamaño]

Aquí es una solución más general:

template<typename T, size_t N> 
void copy_from_array(vector<T> &target_vector, const T (&source_array)[N]) { 
    target_vector.assign(source_array, source_array+N); 
} 

Este funciona porque la matriz se pasa como referencia-a-una-matriz. En C/C++, no puede pasar una matriz como una función, sino que se degradará a un puntero y perderá el tamaño. Pero en C++, puede pasar una referencia a la matriz.

Pasar una matriz por referencia requiere que los tipos coincidan exactamente. El tamaño de una matriz es parte de su tipo. Esto significa que podemos usar el parámetro de la plantilla N para conocer el tamaño para nosotros.

Puede ser aún más simple tener esta función que devuelve un vector. Con las optimizaciones de compilador adecuadas en efecto, esto debería ser más rápido de lo que parece.

template<typename T, size_t N> 
vector<T> convert_array_to_vector(const T (&source_array)[N]) { 
    return vector<T>(source_array, source_array+N); 
} 
+1

En el último ejemplo, 'return {begin (source_array), end (source_array)};' también es posible –

10

Bueno, Pavel estuvo cerca, pero hay incluso una solución más simple y elegante para inicializar un contenedor secuencial de una serie de estilo c.

En su caso:

w_ (array, std::end(array)) 
  • gama nos llevará a un puntero al comienzo de la matriz (no coger su nombre),
  • std :: end (array) recibirá us un iterador al final de la matriz.
+1

¿Qué incluye/versión de C++ requiere esto? – Vlad

+1

Este es uno de los constructores de std :: vector desde al menos C++ 98 en adelante ... Se llama 'range constructor'. http://www.cplusplus.com/reference/vector/vector/vector/ Pruébelo. – Mugurel

+1

La versión más independiente es: w_ (std :: begin (array), std :: end (array)); (En el futuro puede cambiar una matriz C para un contenedor C++). –

Cuestiones relacionadas