push_back example español ejemplo c++ std stdvector

c++ - example - ¿Es una buena práctica usar std:: vector como un simple buffer?



vector c++ example (8)

Tengo una aplicación que está procesando algunas imágenes.

Dado que conozco el ancho / alto / formato, etc. (Sí), y estoy pensando en definir un búfer para almacenar los datos de píxeles:

Entonces, en lugar de usar new y delete [] en un unsigned char* y mantener una nota por separado del tamaño del búfer, estoy pensando en simplificar las cosas usando un std::vector .

Así que declararía mi clase algo como esto:

#include <vector> class MyClass { // ... etc. ... public: virtual void OnImageReceived(unsigned char *pPixels, unsigned int uPixelCount); private: std::vector<unsigned char> m_pImageBuffer; // buffer for 8-bit pixels // ... etc. ... };

Luego, cuando recibí una nueva imagen (de un tamaño variable, pero no se preocupe por esos detalles aquí), puedo cambiar el tamaño del vector (si es necesario) y copiar los píxeles:

void MyClass::OnImageReceived(unsigned char *pPixels, unsigned int uPixelCount) { // called when a new image is available if (m_pImageBuffer.size() != uPixelCount) { // resize image buffer m_pImageBuffer.reserve(uPixelCount); m_pImageBuffer.resize(uPixelCount, 0); } // copy frame to local buffer memcpy_s(&m_pImageBuffer[0], m_pImageBuffer.size(), pPixels, uPixelCount); // ... process image etc. ... }

Esto me parece bien, y me gusta el hecho de que no tengo que preocuparme por la administración de la memoria, pero plantea algunas preguntas:

  1. ¿Es esta una aplicación válida de std::vector o hay un contenedor más adecuado?
  2. ¿Estoy haciendo lo correcto en cuanto a rendimiento llamando a reserve y resize ?
  3. ¿ Siempre será el caso de que la memoria subyacente sea consecutiva, así que puedo usar memcpy_s como se muestra?

Cualquier comentario, crítica o consejo adicional sería muy bienvenido.


  1. Claro, esto funcionará bien. Lo único que debe preocuparse es asegurarse de que el búfer está alineado correctamente, si su clase se basa en una alineación particular; en este caso, es posible que desee utilizar un vector del tipo de datos en sí (como float ).
  2. No, la reserva no es necesaria aquí; cambiar el tamaño aumentará automáticamente la capacidad según sea necesario, exactamente de la misma manera.
  3. Antes de C ++ 03, técnicamente no (pero en la práctica sí). Desde C ++ 03, sí.

A propósito, sin embargo, memcpy_s no es el enfoque idiomático aquí. Use std::copy lugar. Tenga en cuenta que un puntero es un iterador.


Además de lo que otras respuestas mencionan, te recomendaría usar std::vector::assign lugar de std::vector::resize memcpy y memcpy :

void MyClass::OnImageReceived(unsigned char *pPixels, unsigned int uPixelCount) { m_pImageBuffer.assign(pPixels, pPixels + uPixelCount); }

Eso cambiará el tamaño si es necesario, y usted estaría evitando la inicialización 0 innecesaria del buffer causada por std::vector::resize .


Además, para garantizar un mínimo de memoria asignada:

void MyClass::OnImageReceived(unsigned char *pPixels, unsigned int uPixelCount) { m_pImageBuffer.swap(std::vector<unsigned char>( pPixels, pPixels + uPixelCount)); // ... process image etc. ... }

vector :: assign no cambia la cantidad de memoria asignada, si la capacidad es mayor que la cantidad necesaria:

Efectos: borrar (begin (), end ()); insertar (comenzar (), primero, último);


Depende. Si accede a los datos solo a través de iteradores y el operador [], entonces está bien usar un vector.

Si tiene que dar un puntero a funciones que esperan un búfer de, por ejemplo, bytes. No es en mi opinión En este caso, deberías usar algo como

unique_ptr<unsigned char[]> buf(new unsigned char[size])

es tan fácil como un vector, pero en lugar de un vector tienes el control máximo del buffer. Un vector puede reasignar un búfer o, durante una llamada a un método / función, puede hacer involuntariamente una copia de su vector completo. Un error fácil de hacer.

La regla (para mí) es. Si tienes un vector, úsalo como un vector. Si necesita un búfer de memoria, use un búfer de memoria.

Como en un comentario señalado, el vector tiene un método de datos. Esto es C ++. La libertad de usar un vector como un buffer en bruto no repara que deba usarlo como un buffer en bruto. En mi humilde opinión, la intención de un vector era tener un buffer de tipo salvar con el tipo de sistema de acceso de guardado. Para compatibilidad, puede usar el buffer interno para llamadas. La intención no era usar el vector como un contenedor de memoria intermedia de puntero inteligente. Para eso, uso las plantillas de punteros, señalo a otros usuarios de mi código que utilizo este buffer de forma cruda. Si utilizo vectores, los utilizo de la manera en que están destinados, no las posibles formas que ofrecen.

Como tengo algo de culpa aquí por mi opinión (no recomendación), quiero agregar algunas palabras al problema real que describió la operación.

Si espera siempre el mismo tamaño de imagen, debería, en mi opinión, usar un unique_ptr, porque eso es lo que está haciendo con él en mi opinión. Utilizando

m_pImageBuffer.resize(uPixelCount, 0);

pone a cero el búfer primero antes de copiar el pPixel, una penalización de tiempo innecesaria.

Si las imágenes que espera de diferente tamaño, él debería, en mi opinión, no usar un vector durante el siguiente motivo. Especialmente en su código:

// called when a new image is available if (m_pImageBuffer.size() != uPixelCount) { // resize image buffer m_pImageBuffer.reserve(uPixelCount); m_pImageBuffer.resize(uPixelCount, 0); }

cambiará el tamaño del vector, que de hecho es un malloc y copiará mientras las imágenes se agranden. Un realloc en mi experiencia siempre me lleva a malloc y copia.

Esa es la razón por la que yo, especialmente en esta situación, recomiendo el uso de un unique_ptr en lugar de un vector.


Evitaría std :: vector como contenedor para almacenar un buffer no estructurado, ya que std :: vector es profundamente lento cuando se usa como buffer

Considera este ejemplo:

#include <chrono> #include <ctime> #include <iostream> #include <memory> #include <vector> namespace { std::unique_ptr<unsigned char[]> allocateWithPtr() { return std::unique_ptr<unsigned char[]>(new unsigned char[4000000]); } std::vector<unsigned char> allocateWithVector() { return std::vector<unsigned char>(4000000); } } int main() { auto start = std::chrono::system_clock::now(); for (long i = 0; i < 1000; i++) { auto myBuff = allocateWithPtr(); } auto ptr_end = std::chrono::system_clock::now(); for (long i = 0; i < 1000; i++) { auto myBuff = allocateWithVector(); } auto vector_end = std::chrono::system_clock::now(); std::cout << "std::unique_ptr = " << (ptr_end - start).count() / 1000.0 << " ms." << std::endl; std::cout << "std::vector = " << (vector_end - ptr_end).count() / 1000.0 << " ms." << std::endl; }

Salida:

bash-3.2$ time myTest std::unique_ptr = 0.396 ms. std::vector = 35341.1 ms. real 0m35.361s user 0m34.932s sys 0m0.092s

Incluso sin escrituras ni reasignaciones, std :: vector es casi 100,000 veces más lento que el simple uso de un nuevo con un unique_ptr. ¿Que está pasando aqui?

Como señala @MartinSchlott, no está diseñado para esta tarea. Un vector es para contener un conjunto de instancias de objeto, no un buffer no estructurado (desde un punto de vista de matriz). Los objetos tienen destructores y constructores. Cuando se destruye el vector, llama al destructor para cada elemento en él, incluso el vector llamará a un destructor para cada char en su vector.

Puede ver cuánto tiempo se necesita para "destruir" los caracteres sin signo en este vector con este ejemplo:

#include <chrono> #include <ctime> #include <iostream> #include <memory> #include <vector> std::vector<unsigned char> allocateWithVector() { return std::vector<unsigned char>(4000000); } } int main() { auto start = std::chrono::system_clock::now(); for (long i = 0; i < 100; i++) { auto leakThis = new std::vector<unsigned char>(allocateWithVector()); } auto leak_end = std::chrono::system_clock::now(); for (long i = 0; i < 100; i++) { auto myBuff = allocateWithVector(); } auto vector_end = std::chrono::system_clock::now(); std::cout << "leaking vectors: = " << (leak_end - start).count() / 1000.0 << " ms." << std::endl; std::cout << "destroying vectors = " << (vector_end - leak_end).count() / 1000.0 << " ms." << std::endl; }

Salida:

leaking vectors: = 2058.2 ms. destroying vectors = 3473.72 ms. real 0m5.579s user 0m5.427s sys 0m0.135s

Incluso al eliminar la destrucción del vector, todavía demora 2 segundos para construir 100 de estas cosas.

Si no necesita cambio de tamaño dinámico, o construcción y destrucción de los elementos que componen su buffer, no use std :: vector.


Por favor, considera esto:

void MyClass::OnImageReceived(unsigned char *pPixels, unsigned int uPixelCount) { // called when a new image is available if (m_pImageBuffer.size() != uPixelCount) // maybe just < ?? { std::vector<unsigned char> temp; temp.reserve(uPixelCount); // no initialize m_pImageBuffer.swap(temp) ; // no copy old data } m_pImageBuffer.assign(pPixels, pPixels + uPixelCount); // no reallocate // ... process image etc. ... }

Mi punto es que si tienes una imagen grande y necesitas una foto más grande, tu foto anterior tendrá copia durante la reserva y / o cambiará el tamaño en la nueva memoria asignada, se inicializará el exceso de memmory y luego se reescribirá con la nueva foto. Usted coludita directamente, pero luego no podrá usar la información que tiene sobre el nuevo tamaño para evitar posibles reasignaciones (tal vez la implementación de asignar ya está optimizada para este caso simple ????).


Usar un vector en este caso está bien. En C ++, se garantiza que el almacenamiento sea contigioso.

No cambiaría el resize ni reserve , ni tampoco copiaría los datos. En su lugar, todo lo que debes hacer es reserve para asegurarte de que no tienes que reasignar muchas veces, luego borra el vector usando clear . Si resize , pasará y establecerá los valores de cada elemento en sus valores predeterminados; esto es innecesario porque simplemente vas a sobrescribirlo de todos modos.

Cuando esté listo para copiar los datos, no use memcpy . Usa copy junto con back_inserter en un vector vacío:

std::copy (pPixels, pPixels + uPixelCount, std::back_inserter(m_pImageBuffer));

Considero que este modismo es mucho más canónico que el método memcpy que estás empleando. Puede haber métodos más rápidos o más eficientes, pero a menos que pueda probar que esto es un cuello de botella en su código (que probablemente no sea así, tendrá peces mucho más grandes para freír en otro lugar) Me quedaría con métodos idiomáticos y me iría las micro optimizaciones prematuras a otra persona.


std :: vector fue HECHO para ser utilizado en tales casos. Entonces sí.

  1. Sí lo es.

  2. reserve es innecesario en su caso.

  3. Sí, lo hará.