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

Rog*_*and 50 c++ std stdvector

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

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

然后,而不是使用newdelete []unsigned char*并保持一个单独的说明缓冲区大小的,我想通过使用简化的事情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. ...
};
Run Code Online (Sandbox Code Playgroud)

然后,当我收到一个新图像(一些可变大小 - 但不要担心这些细节)时,我可以调整矢量大小(如果需要)并复制像素:

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. ...
}
Run Code Online (Sandbox Code Playgroud)

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

  1. 这是一个有效的应用程序std::vector还是有更合适的容器?
  2. 我是否通过电话reserve 表现来做正确的表演resize
  3. 它将始终是潜在的内存是连续的,所以我可以使用的情况下memcpy_s,如图?

任何其他评论,批评或建议都会非常受欢迎.

Sne*_*tel 35

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

顺便提一下,memcpy_s这里不是惯用法.请std::copy改用.请记住,指针是迭代器.

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

  • 只提供一个特定的分配器.如果要对齐__m128,只需创建__m128s的向量. (2认同)
  • 3.是错误的,这在C++03中也得到了保证。您正在考虑“std::string”。 (2认同)
  • 这就是我所说的——从 C++03 开始​​保证,但在 C++03 之前不保证。相比之下,C++98 中则无法保证这一点。 (2认同)

mfo*_*ini 21

除了提到的其他答案,我建议你使用std::vector::assign而不是std::vector::resizememcpy:

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

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

  • @JohnDibling但是如果你正在使用RandomAccessIterators,那么你*现在可以*现在有多少元素在恒定时间内.我的意思是,标准可能没有明确说明,但如果编译器没有考虑到这种优化,我会感到很惊讶. (3认同)
  • @Slava怎么不导致0初始化?如果新的大小大于旧的大小,则新元素将被初始化值(对于usnigned字符初始化为0)或者在OP的情况下使用第二个参数初始化. (2认同)
  • 当然,您可以通过使用“ reserve”然后“ assign”来强制进行优化。 (2认同)

Joh*_*ing 15

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

我不会既resizereserve,也不会我memcpy在复制数据.相反,所有你需要做的是reserve确保你没有重新分配很多次,然后清除出vector使用clear.如果你resize,它会经历并将每个元素的值设置为默认值 - 这在这里是不必要的,因为你无论如何都要覆盖它.

当您准备复制数据时,请不要使用memcpy.使用copy结合back_inserter到一个空的vector:

std::copy (pPixels, pPixels + uPixelCount, std::back_inserter(m_pImageBuffer));
Run Code Online (Sandbox Code Playgroud)

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

  • 同意.你花在这上面的大脑时间可以更好地使用O(n ^ 2)代码块重构到O(n log n). (2认同)

Ste*_*erg 7

我会避免将 std::vector 作为存储非结构化缓冲区的容器,因为 std::vector 在用作缓冲区时非常慢

考虑这个 (C++14) 示例(对于 C++11,您可以使用共享而不是唯一的 ptr,但是您会注意到数组示例中的轻微性能下降,您在以下位置运行时不会从向量中获得 - O3 或 -O2):

#include <array>
#include <chrono>
#include <ctime>
#include <iostream>
#include <memory>
#include <vector>

namespace {
std::unique_ptr<std::array<unsigned char, 4000000>> allocateWithPtr() {
  return std::make_unique<std::array<unsigned char, 4000000>>();
}

std::vector<unsigned char> allocateWithVector() {
  return std::vector<unsigned char>(4000000);
}
} // namespace

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;
}
Run Code Online (Sandbox Code Playgroud)

输出:

bash % clang++ -O3 -std=gnu++14 test.cpp && ./a.out
std::unique_ptr = 0 ms.
std::vector = 0 ms

bash % clang++ -O2 -std=gnu++14 test.cpp && ./a.out
std::unique_ptr = 0 ms.
std::vector = 0 ms.

bash % clang++ -O1 -std=gnu++14 test.cpp && ./a.out
std::unique_ptr = 89.945 ms.
std::vector = 14135.3 ms.

bash % clang++ -O0 -std=gnu++14 test.cpp && ./a.out
std::unique_ptr = 80.945 ms.
std::vector = 67521.1 ms.
Run Code Online (Sandbox Code Playgroud)

即使没有写入或重新分配,std::vector 也比在 -O0 处使用带有 unique_ptr 的 new 慢 800 多倍,在 -O1 处慢 150 倍。这里发生了什么?

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

您可以通过以下示例查看“销毁”此向量中的无符号字符所需的时间:

#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;
}
Run Code Online (Sandbox Code Playgroud)

输出:

leaking vectors: = 2058.2 ms.
destroying vectors = 3473.72 ms.

real    0m5.579s
user    0m5.427s
sys 0m0.135s
Run Code Online (Sandbox Code Playgroud)

即使去除了vector的破坏,仅仅构建100个这样的东西仍然需要2秒。

如果您不需要动态调整大小或构造和销毁构成缓冲区的元素,请不要使用 std::vector。