¿Cómo inicializar std :: vector desde la matriz de estilo C?

174

¿Cuál es la forma más barata de inicializar una std::vectormatriz de estilo C?

Ejemplo: en la siguiente clase, tengo un vector, pero debido a restricciones externas, los datos se pasarán 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 recorrer los elementos, o llamar std::copy(). ¿Hay mejores métodos?

Franco
fuente
11
El quid del problema es que no hay forma de que el vector sepa si se utilizó el mismo asignador para crear su matriz de estilo C. Como tal, el vector debe asignar memoria utilizando su propio asignador. De lo contrario, simplemente podría cambiar la matriz subyacente y reemplazarla con su matriz.
Nulo

Respuestas:

233

No olvides que puedes tratar los punteros como iteradores:

w_.assign(w, w + len);
Pavel Minaev
fuente
3
Es un problema de calidad de implementación. Dado que los iteradores tienen etiquetas que especifican sus categorías, una implementación de assignes ciertamente libre de usarlos para optimizar; al menos en VC ++, de hecho hace exactamente eso.
Pavel Minaev
38
La solución rápida podría ser std :: vector <double> w_ (w, w + len);
jamk
1
Esto copia elementos en un almacenamiento recién creado para 'w_'; 'w_.data' no apuntará a 'w'. Todavía tienes que desasignar 'w'. No hay transferencia de propiedad
Indy9000
1
Si es un elemento más allá del final, debería estar bien (al igual v.end()que un iterador que señala uno más allá del final con el vector en un caso similar). Si obtienes una afirmación, entonces algo está mal en otra parte.
Pavel Minaev
1
Solo rápidamente, ¿esto desasignará la memoria de la matriz cuando el vector se salga del alcance?
Adrian Koch
41

Utiliza la palabra inicializar, por lo que no está claro si se trata de una tarea única o puede suceder varias veces.

Si solo necesita una inicialización única, puede ponerla en el constructor y usar el constructor de dos iteradores de vectores:

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

De lo contrario, utilice asignar como se sugirió anteriormente:

void set_data(double* w, int len)
{
    w_.assign(w, w + len);
}
Mark B
fuente
1
En mi caso, la asignación sucederá repetidamente.
Frank
12

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

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 como se indicó anteriormente. Todavía acepta una matriz de estilo C como primer argumento. Simplemente lo toma por referencia.


Cómo funciona

[Actualización: vea aquí para una discusión más completa sobre el aprendizaje del tamaño]

Aquí hay 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);
}

Esto funciona porque la matriz se pasa como una referencia a una matriz. En C / C ++, no puede pasar una matriz como una función, sino que se descompondrá en un puntero y perderá el tamaño. Pero en C ++, tú puedes 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 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 apropiadas del compilador 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);
}
Aaron McDaid
fuente
1
En la última muestra, return { begin(source_array), end(source_array) };también es posible
MM
12

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

En tu caso:

w_ (array, std::end(array))
  • array nos dará un puntero al comienzo de la matriz (no capturó su nombre),
  • std :: end (array) nos dará un iterador al final de la matriz.
Mugurel
fuente
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 'constructor de rango'. 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 ++).
Andrew Romanov
13
Eso sí, esto solo funciona si tiene una arraymatriz real (lo que generalmente significa que está copiando desde una matriz global o local (declarada en la función actual)). En el caso del OP, está recibiendo un puntero y una longitud, y debido a que no está tentado en la longitud, no pueden cambiar a recibir un puntero a una matriz de tamaño ni nada, por std::endlo que no funcionará.
ShadowRanger
2
vectorno se sobrecarga operator(), por lo que esto no se compilará. std::endser llamado en un puntero tampoco sirve (la pregunta pide asignar un vector desde un puntero y una variable de longitud separada).
MM
2

La respuesta genérica rápida:

std::vector<double> vec(carray,carray+carray_size); 

o pregunta específica:

std::vector<double> w_(w,w+len); 

basado en lo anterior : no olvide que puede tratar los punteros como iteradores

eusoubrasileiro
fuente
0

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

Me temo que no hay una forma más rápida de implementar (re) inicializar su std::vector.

Janusz Lenar
fuente
¿Qué pasa si los datos se comparten entre el vector y una matriz? ¿Necesitamos copiar algo en este caso?
Vlad
¿Es eso una respuesta o una pregunta? ¿Qué aporta a las respuestas ya existentes?
Jean-François Fabre
@ Jean-FrançoisFabre y ¿qué aporta tu comentario? ;) cierto, es una respuesta pobre dada hace años.
Janusz Lenar