¿Cómo se copia el contenido de una matriz en un std :: vector en C ++ sin bucles?

122

Tengo una matriz de valores que se pasa a mi función desde una parte diferente del programa que necesito almacenar para su posterior procesamiento. Como no sé cuántas veces se llamará a mi función antes de que sea el momento de procesar los datos, necesito una estructura de almacenamiento dinámica, por lo que elegí una std::vector. No quiero tener que hacer el ciclo estándar para push_backtodos los valores individualmente, sería bueno si pudiera copiarlo todo usando algo similar a memcpy.

verdad
fuente

Respuestas:

117

Si puede construir el vector después de obtener la matriz y el tamaño de la matriz, simplemente puede decir:

std::vector<ValueType> vec(a, a + n);

... asumiendo que aes su matriz y nes la cantidad de elementos que contiene. De lo contrario, std::copy()w / resize()hará el truco.

Me mantendría alejado a memcpy()menos que pueda estar seguro de que los valores son tipos de datos antiguos (POD).

Además, vale la pena señalar que ninguno de estos realmente evita el bucle for; es solo una cuestión de si debe verlo en su código o no. O (n) rendimiento en tiempo de ejecución es inevitable para copiar los valores.

Por último, tenga en cuenta que las matrices de estilo C son contenedores perfectamente válidos para la mayoría de los algoritmos STL: el puntero sin formato es equivalente a begin(), y ( ptr + n) es equivalente a end().

Drew Hall
fuente
4
La razón por la que hacer un bucle y llamar a push_back es malo es porque puede obligar al vector a cambiar el tamaño varias veces si la matriz es lo suficientemente larga.
bradtgmurray
@bradtgmurray: Creo que cualquier implementación razonable del constructor de vectores de "dos iteradores" que sugerí anteriormente llamaría a std :: distance () primero en los dos iteradores para obtener la cantidad necesaria de elementos y luego asignarlos solo una vez.
Drew Hall
4
@bradtgmurray: Incluso push_back () no sería tan malo debido al crecimiento exponencial de los vectores (también conocido como "tiempo constante amortizado"). Creo que el tiempo de ejecución solo sería del orden de 2 veces peor en el peor de los casos.
Drew Hall
2
Y si el vector ya está ahí, vec.clear (); vec.insert (vec.begin (), a, a + n); también funcionaría. Entonces ni siquiera necesitaría que a sea un puntero, solo un iterador, y la asignación del vector sería general fallida (y la forma C ++ / STL).
MP24
6
Otra alternativa cuando no se puede construir sería asignar :, vec.assign(a, a+n)que sería más compacto que copiar y redimensionar.
mMontu
209

Ha habido muchas respuestas aquí y casi todas harán el trabajo.

Sin embargo, ¡hay algunos consejos engañosos!

Estas son las opciones:

vector<int> dataVec;

int dataArray[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
unsigned dataArraySize = sizeof(dataArray) / sizeof(int);

// Method 1: Copy the array to the vector using back_inserter.
{
    copy(&dataArray[0], &dataArray[dataArraySize], back_inserter(dataVec));
}

// Method 2: Same as 1 but pre-extend the vector by the size of the array using reserve
{
    dataVec.reserve(dataVec.size() + dataArraySize);
    copy(&dataArray[0], &dataArray[dataArraySize], back_inserter(dataVec));
}

// Method 3: Memcpy
{
    dataVec.resize(dataVec.size() + dataArraySize);
    memcpy(&dataVec[dataVec.size() - dataArraySize], &dataArray[0], dataArraySize * sizeof(int));
}

// Method 4: vector::insert
{
    dataVec.insert(dataVec.end(), &dataArray[0], &dataArray[dataArraySize]);
}

// Method 5: vector + vector
{
    vector<int> dataVec2(&dataArray[0], &dataArray[dataArraySize]);
    dataVec.insert(dataVec.end(), dataVec2.begin(), dataVec2.end());
}

Para abreviar una historia larga, el Método 4, usar vector :: insert, es el mejor para el escenario de bsruth.

Aquí hay algunos detalles sangrientos:

Método 1 es probablemente el más fácil de entender. Simplemente copie cada elemento de la matriz y empújelo en la parte posterior del vector. Por desgracia, es lento. Debido a que hay un bucle (implícito con la función de copia), cada elemento debe tratarse individualmente; no se pueden realizar mejoras de rendimiento basadas en el hecho de que sabemos que la matriz y los vectores son bloques contiguos.

El método 2 es una mejora de rendimiento sugerida para el método 1; simplemente reserve previamente el tamaño de la matriz antes de agregarla. Para arreglos grandes, esto podría ayudar. Sin embargo, el mejor consejo aquí es que nunca use la reserva a menos que el perfil sugiera que puede obtener una mejora (o debe asegurarse de que sus iteradores no se invaliden). Bjarne está de acuerdo . Por cierto, descubrí que este método funcionaba más lento la mayor parte del tiempo, aunque estoy luchando por explicar de manera integral por qué era regularmente significativamente más lento que el método 1 ...

El método 3 es la solución de la vieja escuela: ¡arroje algo de C al problema! Funciona bien y rápido para tipos POD. En este caso, es necesario llamar a resize ya que memcpy funciona fuera de los límites del vector y no hay forma de decirle a un vector que su tamaño ha cambiado. Además de ser una solución desagradable (¡copia de bytes!), Recuerde que esto solo se puede usar para tipos POD . Nunca usaría esta solución.

El método 4 es la mejor manera de hacerlo. Su significado es claro, es (generalmente) el más rápido y funciona para cualquier objeto. No hay inconveniente en utilizar este método para esta aplicación.

Método 5 es una modificación del Método 4: copia la matriz en un vector y luego agrégala. Buena opción, generalmente rápida y clara.

Finalmente, sabe que puede usar vectores en lugar de matrices, ¿verdad? Incluso cuando una función espera matrices de estilo c, puede usar vectores:

vector<char> v(50); // Ensure there's enough space
strcpy(&v[0], "prefer vectors to c arrays");

¡Espero que eso ayude a alguien!

MattyT
fuente
6
No se puede hacer referencia de forma segura y portátil a "& dataArray [dataArraySize]"; está desreferenciando un puntero / iterador más allá del final. En su lugar, puede decir dataArray + dataArraySize para obtener el puntero sin tener que eliminar la referencia primero.
Drew Hall
2
@Drew: sí, se puede, al menos en C. Se define que &exprno evalúa expr, solo computa la dirección del mismo. Y un puntero uno más allá del último elemento es perfectamente válido, también.
Roland Illig
2
¿Ha intentado hacer el método 4 con 2? es decir, reservando el espacio antes de insertar. Parece que si el tamaño de los datos es grande, múltiples inserciones necesitarán múltiples reasignaciones. Como conocemos el tamaño a priori, podemos hacer la reasignación, antes de insertar.
Jorge Leitao
2
@MattyT ¿cuál es el punto del método 5? ¿Por qué hacer una copia intermedia de los datos?
Ruslan
2
Personalmente, preferiría beneficiarme de que las matrices se conviertan en punteros automáticamente: me dataVec.insert(dataVec.end(), dataArray, dataArray + dataArraySize);parece mucho más claro. Tampoco se puede obtener nada del método 5, solo parece bastante ineficiente, a menos que el compilador pueda optimizar el vector nuevamente.
Aconcagua
37

Si todo lo que está haciendo es reemplazar los datos existentes, puede hacer esto

std::vector<int> data; // evil global :)

void CopyData(int *newData, size_t count)
{
   data.assign(newData, newData + count);
}
Torlack
fuente
1
Simple de entender y definitivamente la solución más rápida (es solo una memoria detrás de escena).
Don Scott
¿Deta.assign es más rápido que data.insert?
Jim
10

Como solo puedo editar mi propia respuesta, voy a hacer una respuesta compuesta de las otras respuestas a mi pregunta. Gracias a todos los que respondieron.

Usando std :: copy , esto aún se repite en segundo plano, pero no tiene que escribir el código.

int foo(int* data, int size)
{
   static std::vector<int> my_data; //normally a class variable
   std::copy(data, data + size, std::back_inserter(my_data));
   return 0;
}

Usando memcpy normal . Probablemente se utilice mejor para tipos de datos básicos (es decir, int) pero no para matrices más complejas de estructuras o clases.

vector<int> x(size);
memcpy(&x[0], source, size*sizeof(int));
verdad
fuente
Iba a recomendar este enfoque.
mmocny
Lo más probable es que sea más eficaz cambiar el tamaño de su vector por adelantado si conoce el tamaño de antemano y no usa el back_inserter.
luke
podría agregar my_data.reserve (tamaño)
David Nehme
Tenga en cuenta que internamente esto está haciendo exactamente lo que parece querer evitar. No está copiando bits, solo está haciendo un bucle y llamando a push_back (). ¿Supongo que solo querías evitar escribir el código?
mmocny
1
¿Por qué no usas el constructor de vectores para copiar los datos?
Martin York
3

Evita la memoria, digo. No hay razón para meterse con las operaciones del puntero a menos que sea necesario. Además, solo funcionará para tipos POD (como int), pero fallará si se trata de tipos que requieren construcción.

Assaf Lavie
fuente
8
Quizás esto debería ser un comentario sobre una de las otras respuestas, ya que en realidad no propone una solución.
finnw
3
int dataArray[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };//source

unsigned dataArraySize = sizeof(dataArray) / sizeof(int);

std::vector<int> myvector (dataArraySize );//target

std::copy ( myints, myints+dataArraySize , myvector.begin() );

//myvector now has 1,2,3,...10 :-)
Antonio Ramasco
fuente
2
Si bien este fragmento de código es bienvenido y puede proporcionar algo de ayuda, sería mucho mejor si incluyera una explicación de cómo y por qué esto resuelve el problema. Recuerde que está respondiendo la pregunta para los lectores en el futuro, ¡no solo la persona que pregunta ahora! Por favor, editar su respuesta para agregar explicación y dar una indicación de lo que se aplican limitaciones y supuestos.
Toby Speight
4
Espera, ¿qué myints?
mavavilj
2

Otra respuesta más, dado que la persona dijo "No sé cuántas veces se llamará a mi función", podría usar el método de inserción de vectores de esta manera para agregar matrices de valores al final del vector:

vector<int> x;

void AddValues(int* values, size_t size)
{
   x.insert(x.end(), values, values+size);
}

Me gusta de esta manera porque la implementación del vector debería poder optimizarse para la mejor manera de insertar los valores según el tipo de iterador y el tipo en sí. De alguna manera está respondiendo sobre la implementación de stl.

Si necesita garantizar la velocidad más rápida y sabe que su tipo es un tipo POD, le recomendaría el método de cambio de tamaño en la respuesta de Thomas:

vector<int> x;

void AddValues(int* values, size_t size)
{
   size_t old_size(x.size());
   x.resize(old_size + size, 0);
   memcpy(&x[old_size], values, size * sizeof(int));
}
Shane Powell
fuente
1

Además de los métodos presentados anteriormente, debe asegurarse de usar std :: Vector.reserve (), std :: Vector.resize (), o construir el vector al tamaño, para asegurarse de que su vector tenga suficientes elementos en para guardar sus datos. si no, dañará la memoria. Esto es cierto para std :: copy () o memcpy ().

Esta es la razón para usar vector.push_back (), no puede escribir más allá del final del vector.

Thomas Jones-Low
fuente
Si está utilizando un back_inserter, no necesita reservar previamente el tamaño del vector al que está copiando. back_inserter hace un push_back ().
John Dibling
0

Suponiendo que sepa qué tan grande es el elemento del vector:

std::vector<int> myArray;
myArray.resize (item_count, 0);
memcpy (&myArray.front(), source, item_count * sizeof(int));

http://www.cppreference.com/wiki/stl/vector/start

Thomas Jones-Low
fuente
¿No depende eso de la implementación de std :: vector?
ReaperUnreal
¡Esto es horrible! Está llenando la matriz dos veces, una con "0" y luego con los valores adecuados. Simplemente haga: std :: vector <int> myArray (fuente, fuente + item_count); y confíe en su compilador para producir memcpy!
Chris Jefferson
Confíe en su compilador para producir __memcpy_int_aligned; eso debería ser aún más rápido
MSalters