使用std :: vector作为一个简单的缓冲区是不错的做法吗?
我有一个应用程序正在执行一些image processing。
考虑到我知道宽度/高度/格式等(我这样做),并考虑定义缓冲区来存储像素数据:
然后,而不是在unsigned char*
上使用new
和delete []
,并保留一个单独的缓冲区大小的笔记,我想通过使用std::vector
来简化事情。
所以我会声明我的类是这样的:
#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. ... };
然后,当我收到一个新的图像(某些可变大小的图像 – 但不用担心这些细节)时,我可以调整vector大小(如有必要)并复制像素:
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. ... }
这对我来说似乎很好,我喜欢这个事实,我不必担心内存pipe理,但是它提出了一些问题:
- 这是一个有效的
std::vector
应用程序还是有一个更适合的容器? - 我是否通过调用
reserve
和resize
来在性能方面做正确的事情? - 将始终是底层的内存是连续的,所以我可以使用
memcpy_s
所示?
任何额外的评论,批评或build议将是非常受欢迎的。
- 当然,这会正常工作。 你需要担心的一件事是确保缓冲区正确alignment,如果你的类依赖于特定的alignment; 在这种情况下,您可能需要使用数据types本身的向量(如
float
)。 - 不,在这里不需要储备。 resize将根据需要自动增加容量,方式完全相同。
- 在C + + 03之前,技术上不(但实际上是的)。 从C ++ 03开始,是的。
顺便提一下, memcpy_s
不是这里惯用的方法。 改用std::copy
。 请记住,指针是一个迭代器。
除了其他答案提到,我build议你使用std::vector::assign
而不是std::vector::resize
和memcpy
:
void MyClass::OnImageReceived(unsigned char *pPixels, unsigned int uPixelCount) { m_pImageBuffer.assign(pPixels, pPixels + uPixelCount); }
这将resize,如果有必要,你会避免由std::vector::resize
引起的缓冲区的不必要的0
初始化。
在这种情况下使用vector
是好的。 在C ++中,存储保证是连续的。
我不会resize
和reserve
,也不会memcpy
复制数据。相反,所有您需要做的就是reserve
以确保您不必重新分配多次,然后使用clear
vector
。 如果你resize
,它会通过并设置每个元素的值为默认值 – 这是不必要的,因为无论如何你只要覆盖它。
准备好复制数据时,请勿使用memcpy
。 与back_inserter
一起使用copy
到一个空的vector
:
std::copy (pPixels, pPixels + uPixelCount, std::back_inserter(m_pImageBuffer));
我认为这个成语比你正在使用的memcpy
方法更接近于规范。 有可能是更快或更有效的方法,但除非你能certificate这是你的代码中的瓶颈(这可能不会;你会有更大的鱼在其他地方炒),我会坚持习惯方法,并离开过早的微观优化给别人。
std :: vector已经MADE在这种情况下使用。 所以,是的。
-
是的。
-
你的情况下
reserve
是不必要的。 -
是的,它会。
另外 – 确保最小的分配内存:
void MyClass::OnImageReceived(unsigned char *pPixels, unsigned int uPixelCount) { m_pImageBuffer.swap(std::vector<unsigned char>( pPixels, pPixels + uPixelCount)); // ... process image etc. ... }
vector :: assign不会更改分配的内存量,如果容量大于所需的数量:
效果:擦除(开始(),结束()); 插入(begin(),first,last);
请考虑这个:
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. ... }
我的观点是,如果你有一个大的图片,需要一个更大的PIC照片,你的旧图片将在备用期间得到复制,并/或调整到新分配的内存中,超出内存初始化,然后重新编译新的图片。 你colud直接assing,但那么你将无法使用你有关新的大小的信息,以避免posible重新分配(也许分配的实施已经优化为这个简单的情况????)。
这取决于。 如果仅通过迭代器和[]运算符访问数据,则可以使用vector。
如果你必须给一个函数指针,指向一个例如字节的缓冲区。 这不是在我看来。 在这种情况下,你应该使用类似的东西
unique_ptr<unsigned char[]> buf(new unsigned char[size])
是保存为一个向量,而不是一个向量,你有最大的缓冲区控制。 一个向量可能会重新分配一个缓冲区,或者在方法/函数调用期间,您可能会无意中复制整个向量。 一个容易犯的错误。
规则(对我来说)是。 如果你有一个vector,就像一个vector一样使用它。 如果您需要内存缓冲区,请使用内存缓冲区。
正如在一个评论指出的,该向量有一个数据方法。 这是C ++。 将vector用作原始缓冲区的自由不会改变,您应该将其用作原始缓冲区。 在我看来,vector的意图是具有types保存访问系统的types保存缓冲区。 为了兼容性,您可以使用内部缓冲区进行调用。 其目的不是将该vector用作智能指针缓冲容器。 为此,我使用指针模板,向我的代码的其他用户发出信号,以原始方式使用此缓冲区。 如果我使用vector,我用它们的方式,而不是他们提供的可能的方式。
因为我在这里得到了一些责备我的意见(不build议),我想添加一些话到实际问题的操作描述。
如果他总是期待相同的图片大小,那么我认为他应该使用一个unique_ptr,因为在我看来他就是这么做的。 运用
m_pImageBuffer.resize(uPixelCount, 0);
在将pPixel复制到缓冲区之前,先将缓冲区置零,这是不必要的时间处罚。
如果他所期望的图片大小不一,那么在我看来,他应该在下面的理由中不使用vector。 特别是在他的代码中:
// called when a new image is available if (m_pImageBuffer.size() != uPixelCount) { // resize image buffer m_pImageBuffer.reserve(uPixelCount); m_pImageBuffer.resize(uPixelCount, 0); }
他将调整vector,这实际上是一个malloc和复制,只要图像越来越大。 在我的经验realloc总是导致malloc和复制。
这就是我特别是在这种情况下推荐使用unique_ptr而不是vector的原因。
我会避免将std :: vector作为存储非结构化缓冲区的容器,因为std :: vector在用作缓冲区时速度很慢
考虑这个例子:
#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; }
输出:
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
即使没有写入或重新分配,std :: vector比使用unique_ptr新的速度慢了近10万倍。 这里发生了什么?
正如@MartinSchlott所指出的,它不是为这个任务devise的。 一个向量用于保存一个设置的对象实例,而不是一个非结构化(从数组的立场)缓冲区。 对象具有析构函数和构造函数。 当vector被销毁时,它会为其中的每个元素调用析构函数,甚至vector也会调用vector中的每个char的析构函数。
你可以看到只需要用这个例子“销毁”这个向量中的无符号字符需要多less时间:
#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; }
输出:
leaking vectors: = 2058.2 ms. destroying vectors = 3473.72 ms. real 0m5.579s user 0m5.427s sys 0m0.135s
即使消除vector的破坏,也只需要2秒钟就可以构build100个这样的东西。
如果你不需要dynamicresize,或者构build和破坏构成缓冲区的元素,不要使用std :: vector。