将std :: vector用作简单缓冲区是一种好习惯吗?

时间:2013-10-23 13:06:23

标签: c++ std stdvector

我有一个应用程序正在对某些图像执行某些处理。

鉴于我知道宽度/高度/格式等(我这样做),并考虑定义一个缓冲区来存储像素数据:

然后,我不是在new上使用delete []unsigned char*并且单独记录缓冲区大小,而是考虑使用{{1}来简化事情。 }。

所以我会宣布我的课程是这样的:

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. ...
};

这对我来说似乎很好,我喜欢这个事实,我不必担心内存管理,但它提出了一些问题:

  1. 这是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. ... } 的有效应用还是有更合适的容器?
  2. 我是否通过致电std::vector reserve来表现正确的事情?
  3. 总是是否是基础内存是连续的,所以我可以使用resize,如图所示?
  4. 非常欢迎任何其他评论,批评或建议。

8 个答案:

答案 0 :(得分:36)

  1. 当然,这个工作正常。您需要担心的一件事是确保缓冲区正确对齐,如果您的类依赖于特定的对齐方式;在这种情况下,您可能希望使用数据类型本身的向量(如float)。
  2. 不,这里没有必要保留; resize将根据需要以完全相同的方式自动增加容量。
  3. 在C ++ 03之前,技术上没有(但实际上是的)。从C ++ 03开始,是的。
  4. 顺便说一下,memcpy_s不是这里惯用的方法。请改用std::copy。请记住,指针是一个迭代器。

    从C ++ 17开始,std::byte是不透明类型存储的惯用单元,例如您在此处使用。当然,char仍然有效,但允许使用char不安全的用法(byte!)。

答案 1 :(得分:20)

除了提及其他答案之外,我建议您使用std::vector::assign而不是std::vector::resizememcpy

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

如果需要,这将调整大小,并且您将避免由0引起的缓冲区的不必要的std::vector::resize初始化。

答案 2 :(得分:15)

在这种情况下使用vector很好。在C ++中,存储保证是有余的。

我不会同时resizereserve,也不会memcpy复制数据。相反,您需要做的只是reserve以确保您不必多次重新分配,然后使用vector清除clear。如果你resize,它会通过并将每个元素的值设置为默认值 - 这是不必要的,因为你只是要覆盖它。

当您准备复制数据时,请勿使用memcpy。将copyback_inserter结合使用,放入空vector

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

我认为这个成语比你正在使用的memcpy方法更接近规范。可能有更快或更有效的方法,但除非你能证明这是你的代码的瓶颈(它可能不会;你将有更大的鱼在其他地方煎炸)我会坚持使用惯用的方法并离开对别人过早的微观优化。

答案 3 :(得分:3)

std :: vector是在这种情况下使用的。所以,是的。

  1. 是的,是。

  2. reserve在您的情况下是不必要的。

  3. 是的,它会。

答案 4 :(得分:2)

此外 - 确保分配的内存最少:

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

vector :: assign不会改变分配的内存量,如果容量大于所需的量:

  

效果:       erase(begin(),end());       insert(begin(),first,last);

答案 5 :(得分:2)

请考虑一下:

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. ...
}

我的观点是,如果你有一张大图并且需要一张更大的照片,你的旧照片将在保留期间获得复制和/或调整大小到新分配的内存中,多余的memmory初始化,然后重新填充新照片。你直接帮助colud,但是你将无法使用你有关新大小的信息来避免可靠的重新分配(对于这个简单的情况,可能已经优化了assign的实现)。

答案 6 :(得分:1)

我会避免使用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的new慢近10万倍。这里发生了什么?

正如@MartinSchlott指出的那样,它不是为此任务而设计的。向量用于保存设置对象实例,而不是非结构化(从数组立场)缓冲区。对象具有析构函数和构造函数。 当向量被销毁时,它会为其中的每个元素调用析构函数,甚至vector也会为向量中的每个字符调用析构函数。

你可以看到只需要花多少时间来摧毁&#34;这个例子中这个向量中的无符号字符:

#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

即使删除了矢量的破坏,它仍然需要2秒才能构建100个这样的东西。

如果您不需要动态调整大小,或构建&amp;破坏构成缓冲区的元素,不要使用std :: vector。

答案 7 :(得分:0)

这取决于。 如果只通过迭代器和[]运算符访问数据,那么可以使用向量。

如果你必须给出一个指向期望缓冲区的函数的指针,例如字节。这不是我认为的。在这种情况下,您应该使用类似

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

是保存为矢量,但是您可以最大限度地控制缓冲区而不是矢量。向量可能会重新分配缓冲区,或者在方法/函数调用期间,您可能会无意中复制整个向量。一个容易犯的错误。

规则(对我而言)是。如果你有一个矢量,就像矢量一样使用它。如果需要内存缓冲区,请使用内存缓冲区。

正如评论指出的那样,向量有一个数据方法。这是C ++。使用向量作为原始缓冲区的自由并不意味着您应该将其用作原始缓冲区。在我的拙见中,向量的意图是具有类型保存访问系统的类型保存缓冲区。为了兼容性,您可以使用内部缓冲区进行调用。目的不是将向量用作智能指针缓冲容器。为此,我使用指针模板,向我的代码的其他用户发信号通知我以原始方式使用此缓冲区。如果我使用向量,我会按照它们的预期方式使用它们,而不是它们提供的可能方式。

我在这里得到了一些责备,我的意见(不是推荐)我想在操作中描述的实际问题中添加一些词语。

如果他期望总是相同的图片尺寸,我认为他应该使用unique_ptr,因为在我看来,这就是他正在做的事情。使用

 m_pImageBuffer.resize(uPixelCount, 0);

在将pPixel复制到缓冲区之前先将缓冲区归零,这是一个不必要的时间损失。

如果他想要的图片大小不同,我认为在下列原因中他不应该使用矢量。特别是在他的代码中:

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

他将调整向量的大小,只要图像越来越大,它实际上是一个malloc和副本。根据我的经验,realloc总是会导致malloc和copy。

这就是我,尤其是在这种情况下,建议使用unique_ptr而不是vector的原因。