【问题标题】:A dynamic buffer type in C++?C ++中的动态缓冲区类型?
【发布时间】:2010-12-24 20:46:53
【问题描述】:

我不完全是 C++ 新手,但过去我很少认真处理它,所以我对其设施的了解相当粗略。

我正在用 C++ 编写一个快速的概念验证程序,我需要一个动态大小的二进制数据缓冲区。也就是说,我要从网络套接字接收数据,但我不知道会有多少(虽然不超过几 MB)。我可以自己编写这样的缓冲区,但是如果标准库可能已经有了一些东西,为什么还要打扰呢?我使用的是 VS2008,所以一些微软特定的扩展对我来说很好。我只需要四个操作:

  • 创建缓冲区
  • 将数据写入缓冲区(二进制垃圾,不以零结尾)
  • 将写入的数据作为 char 数组获取(连同其长度)
  • 释放缓冲区

类/函数集/我需要什么的名称是什么?

添加:std::vector 投了几票。一切都很好,但我不想逐字节推送几 MB 的数据。套接字将以几 KB 的大块向我提供数据,所以我想一次将它们全部写入。此外,最后我需要将数据作为简单的 char* 获取,因为我需要将整个 blob 传递给一些未修改的 Win32 API 函数。

【问题讨论】:

  • 您不是逐字节推送。您可以在向量的末尾插入一个数据块。

标签: c++ memory-management stl dynamic buffer


【解决方案1】:

你想要一个std::vector

std::vector<char> myData;

vector 会自动为你分配和释放它的内存。使用push_back 添加新数据(vector 将根据需要为您调整大小),并使用索引运算符[] 检索数据。

如果您在任何时候都能猜出您需要多少内存,我建议您致电reserve,这样后续的push_back 就不必重新分配那么多。

如果你想读入一块内存并将其附加到你的缓冲区,最简单的可能是这样的:

std::vector<char> myData;
for (;;) {
    const int BufferSize = 1024;
    char rawBuffer[BufferSize];

    const unsigned bytesRead = get_network_data(rawBuffer, sizeof(rawBuffer));
    if (bytesRead <= 0) {
        break;
    }

    myData.insert(myData.end(), rawBuffer, rawBuffer + bytesRead);
}

myData 现在拥有所有读取数据,逐块读取。但是,我们复制了两次。

我们改为尝试这样的事情:

std::vector<char> myData;
for (;;) {
    const int BufferSize = 1024;

    const size_t oldSize = myData.size();
    myData.resize(myData.size() + BufferSize);        

    const unsigned bytesRead = get_network_data(&myData[oldSize], BufferSize);
    myData.resize(oldSize + bytesRead);

    if (bytesRead == 0) {
        break;
    }
}

直接读入缓冲区,代价是偶尔会过度分配。

这可以通过例如将每个调整大小的向量大小加倍以摊销调整大小,正如第一个解决方案隐式所做的那样。当然,如果您对最终缓冲区的可能大小有先验知识,您可以预先reserve() 一个更大的缓冲区,以最大限度地减少调整大小。

两者都留给读者作为练习。 :)

最后,如果您需要将数据视为原始数组:

some_c_function(myData.data(), myData.size());

std::vector 保证是连续的。

【讨论】:

  • 好的,但是我没有看到可以添加整个数据缓冲区的成员。还是我必须逐字节推送几 MB?我会从套接字中读取它的几KB 大块。
  • Vilx -- 使用 myData.insert(myData.end(), bytes_ptr, bytes_ptr + bytes_count)
  • 假设你有一个已知大小的缓冲区,vec.insert(vec.end, buf, buf+length)
  • Vector 需要是连续的,因此可以取一个元素的地址,然后 memcopy() 将一个数据块放入其中。随时为这可怕的事情而战栗。
  • 为什么要使用中间缓冲区?为什么不直接将网络数据读入向量中呢?将向量调整为旧大小 +N,接收最大 N 个字节到 &vector[old_vector_size]。
【解决方案2】:
std::vector<unsigned char> buffer;

每个 push_back 都会在最后添加新的字符(如果需要重新分配)。如果您大致知道需要多少数据,则可以调用 reserve 以最小化分配次数。

buffer.reserve(1000000);

如果你有这样的事情:

unsigned char buffer[1000];
std::vector<unsigned char> vec(buffer, buffer + 1000);

【讨论】:

    【解决方案3】:

    std::string 可以解决这个问题:

    • 它支持嵌入空值。
    • 您可以通过在其上调用 append() 并使用指针和长度向其附加多字节数据块。
    • 您可以通过调用data() 获取其内容为char 数组,并通过调用size()length() 获取当前长度。
    • 释放缓冲区由析构函数自动处理,但您也可以在其上调用 clear() 以擦除其内容而不破坏它。

    【讨论】:

    • 但是我们可以在缓冲区中有几个 \0 吗?
    • 是的,这就是我说它支持嵌入式空值时的意思。
    • 有趣,std::string 比我想象的要强大。参看。 stackoverflow.com/a/5319584/587407 +1
    【解决方案4】:

    对 std::vector 再投一票。最少的代码,跳过GMan的额外复制代码:

    std::vector<char> buffer;
    static const size_t MaxBytesPerRecv = 1024;
    size_t bytesRead;
    do
    {
        const size_t oldSize = buffer.size();
    
        buffer.resize(oldSize + MaxBytesPerRecv);
        bytesRead = receive(&buffer[oldSize], MaxBytesPerRecv); // pseudo, as is the case with winsock recv() functions, they get a buffer and maximum bytes to write to the buffer
    
        myData.resize(oldSize + bytesRead); // shrink the vector, this is practically no-op - it only modifies the internal size, no data is moved/freed
    } while (bytesRead > 0);
    

    至于调用 WinAPI 函数 - 使用 &buffer[0] (是的,它有点笨拙,但就是这样)传递给 char* 参数,buffer.size() 作为长度。

    最后一点,你可以使用 std::string 代替 std::vector,应该没有任何区别(除非你可以写 buffer.data() 而不是 &buffer[0] 如果你的缓冲区是字符串)

    【讨论】:

    • +1:如果您选择矢量,这就是这样做的方式。我仍然声称这里的向量只是用作 {size, capacity, pointer} 的集合,你可以很容易地自己调用realloc ...
    • 我声称 C++ 只是一些汇编指令,你应该使用它们。 :P
    • 很公平 ;D 我只是不认为向量在这里增加了太多抽象或表现力——尽管这可能取决于用户/读者对 C 内存分配的舒适程度。
    • @Useless: 好的,那么无忧的异常安全内存管理怎么样?
    • 好的,好点:我习惯于低级套接字编程的惯用 C 代码(并且 POSIX 套接字 API 不会抛出),但它不是一般的好风格或惯用的 C++。
    【解决方案5】:

    我会看看 Boost basic_streambuf,它就是为这种目的而设计的。如果您不能(或不想)使用 Boost,我会考虑使用 std::basic_streambuf,它非常相似,但需要做更多的工作。无论哪种方式,您基本上都是从该基类派生并重载underflow() 以将数据从套接字读取到缓冲区中。您通常会将std::istream 附加到缓冲区,因此其他代码从中读取的方式与用户从键盘(或其他)输入的方式大致相同。

    【讨论】:

      【解决方案6】:

      不是来自 STL 但可能有用的替代方案 - Boost.Circular buffer

      【讨论】:

        【解决方案7】:

        使用std::vector,一个不断增长的数组,保证存储是连续的(你的第三点)。

        【讨论】:

          【解决方案8】:

          关于您的评论“我没有看到 append()”,在末尾插入是一回事。

          vec.insert(vec.end,

          【讨论】:

            【解决方案9】:

            如果您确实使用了 std::vector,那么您只是在使用它来为您管理原始内存。 您可以只malloc 您认为您需要的最大缓冲区,并跟踪到目前为止读取的写入偏移量/总字节数(它们是相同的)。 如果你走到最后……要么realloc要么选择失败的方式。

            我知道,它不是很 C++,但这是一个简单的问题,其他提议似乎是引入不必要副本的重量级方法。

            【讨论】:

            • 嗯,这基本上就是我想要做的。我只是想知道是否有一些内置的方法可以做到这一点。
            【解决方案10】:

            这里的重点是,你想用缓冲区做什么。 如果要保留带有指针的结构,则必须将缓冲区固定在首先分配的内存地址处。 为了避免这种情况,您必须使用相对指针和修复列表在最终分配后更新指针。这将值得一堂。 (没找到这样的东西)。

            【讨论】:

              猜你喜欢
              • 2015-12-10
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2017-10-26
              • 1970-01-01
              相关资源
              最近更新 更多