C ++中的动态缓冲区类型?

时间:2009-12-09 14:43:33

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

我不是一个C ++新手,但我过去几乎没有认真对待它,所以我对它的设施的了解相当粗略。

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

  • 创建缓冲区
  • 将数据写入缓冲区(二进制垃圾,而不是零终止)
  • 将书面数据作为char数组(及其长度)获取
  • 释放缓冲区

我需要的类/功能集的名称是什么?

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

10 个答案:

答案 0 :(得分:39)

您需要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保证是连续的。

答案 1 :(得分:9)

std::vector<unsigned char> buffer;

每个push_back都会在最后添加新的char(如果需要,可以重新分配)。如果您大致知道预期的数据量,可以调用reserve来最小化分配数量。

buffer.reserve(1000000);

如果你有这样的事情:

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

答案 2 :(得分:7)

std::string适用于此:

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

答案 3 :(得分:6)

再次投票给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函数 - 使用&amp; buffer [0](是的,它有点笨拙,但这就是它的方式)传递给char *参数,buffer.size()作为长度。

最后一点,您可以使用std :: string而不是std :: vector,应该没有任何区别(除非您可以编写buffer.data()而不是&amp; buffer [0]是一个字符串)

答案 4 :(得分:4)

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

答案 5 :(得分:2)

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

答案 6 :(得分:1)

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

答案 7 :(得分:0)

关于你的评论“我没有看到附加()”,最后的是相同的。

vec.insert(vec.end,

答案 8 :(得分:0)

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

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

答案 9 :(得分:0)

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