内存管理——用于分配/释放I/O缓冲区的现代c++风格
memory management - Modern C++ idiom for allocating / deallocating an I/O buffer
对于I/O工作,我需要将N个字节读入缓冲区。N在运行时(而不是编译时)是已知的。缓冲区的大小永远不会改变。缓冲区被传递给其他例程来压缩、加密等:它只是一个字节序列,仅此而已。
在C中,我会用malloc
分配缓冲区,然后在完成后用free
分配缓冲区。但是,我的代码是现代c++,当然没有malloc
,很少使用原始的new
和delete
:我大量使用RAII和shared_ptr
。然而,这些技术似乎都不适合这个缓冲区。它只是一个固定长度的字节缓冲区,用于接收I/O并使其内容可用。
有一个现代的c++习语来优雅地做到这一点吗?或者,在这方面,我应该继续使用好的malloc
吗?
基本上,您有两个主要的c++方式选择:
-
std::vector
-
std::unique_ptr
我更喜欢第二种,因为你不需要在std::vector
中自动调整大小的东西,你不需要一个容器-你只需要一个缓冲区。
std::unique_ptr
有一个动态数组的专门化:std::unique_ptr<int[]>
将在它的析构函数中调用delete []
,并提供适当的operator []
。
如果你想要代码:
std::unique_ptr<char[]> buffer(new char [size]);
some_io_function(buffer.get(), size); // get() returnes raw pointer
不幸的是,它没有办法检索缓冲区的大小,因此您必须将其存储在一个变量中。如果它使您感到困惑,那么std::vector
将完成工作:
std::vector<char> buffer(size);
some_io_function(buffer.data(), buffer.size()); // data() returnes raw pointer
如果你想传递缓冲区,这取决于你怎么做。
考虑以下情况:缓冲区在某个地方被填充,然后在其他地方处理,存储一段时间,然后在某个地方写入并销毁。实际上,您并不需要代码中的两个位置来拥有缓冲区,您可以简单地从一个位置到另一个位置std::move
它。对于这个用例,std::unique_ptr
将完美地工作,并将保护您避免偶尔复制缓冲区(而使用std::vector
,您可以错误地复制它,并且不会出现错误或警告)。
相反,如果您需要代码中的多个位置来保存相同的缓冲区(可能它同时在多个位置填充/使用/处理),则肯定需要std::shared_ptr
。不幸的是,它没有类似数组的特化,因此必须传递适当的deleter:
std::shared_ptr<char> buffer(new char[size], std::default_delete<char[]>());
第三个选项是如果你真的需要复制缓冲区。然后,std::vector
将更简单。但是,正如我已经提到的,我觉得这不是最好的方式。此外,您始终可以手动复制std::unique_ptr
或std::shared_ptr
的缓冲区保持,这清楚地记录了您的意图:
std::uniqure_ptr<char[]> buffer_copy(new char[size]);
std::copy(buffer.get(), buffer.get() + size, buffer_copy.get());
在 c++ 14中,有一种语法上非常干净的方法来实现你想要的:
size_t n = /* size of buffer */;
auto buf_ptr = std::make_unique<uint8_t[]>(n);
auto nr = ::read(STDIN_FILENO, buf_ptr.get(), n);
auto nw = ::write(STDOUT_FILENO, buf_ptr.get(), nr);
// etc.
// buffer is freed automatically when buf_ptr goes out of scope
注意,上面的构造将对缓冲区进行值初始化(零化)。如果您想跳过初始化以节省几个周期,则必须使用lisyarus给出的略显难看的形式:
std::unique_ptr<uint8_t[]> buf_ptr(new uint8_t[n]);
c++ 20引入了std::make_unique_for_overwrite
,它允许将上面的非初始化行更简洁地写成:
auto buf_ptr = std::make_unique_for_overwrite<uint8_t[]>(n);
很简单:
std::vector<char> myBuffer(N);
我认为通常使用std::vector。
使用std::vector而不是手动分配的char
缓冲区的好处是复制语义(用于传递给希望根据自己的目的修改数据的函数或将数据返回给调用函数)。
std::vector也知道自己的大小,减少了需要传递给处理函数的形参的数量,消除了bug的来源。
您可以完全控制数据传递给其他函数的方式-根据需要通过引用或const引用。
如果你需要调用一个旧的c风格的函数,用一个普通的char*
和长度,你也可以很容易地做到这一点:
// pass by const reference to preserve data
void print_data(const std::vector<char>& buf)
{
std::cout.fill('0');
std::cout << "0x";
for(auto c: buf)
std::cout << std::setw(2) << std::hex << int(c);
std::cout << 'n';
}
// pass by reference to modify data
void process_data(std::vector<char>& buf)
{
for(auto& c: buf)
c += 1;
}
// pass by copy to modify data for another purpose
void reinterpret_data(std::vector<char> buf)
{
// original data not changed
process_data(buf);
print_data(buf);
}
void legacy_function(const char* buf, std::size_t length)
{
// stuff
}
int main()
{
std::ifstream ifs("file.txt");
// 24 character contiguous buffer
std::vector<char> buf(24);
while(ifs.read(buf.data(), buf.size()))
{
// changes data (pass by reference)
process_data(buf);
// modifies data internally (pass by value)
reinterpret_data(buf);
// non-modifying function (pass by const ref)
print_data(buf);
legacy_function(buf.data(), buf.size());
}
}
使用
std::vector<char> buffer(N)
如果buffer的大小不变,你可以这样使用它作为一个数组:
char * bufPtr = &buffer[0];
这也将在c++ 03中工作。
- Qt VTK交互风格的信号到小部件
- 我可以使用条件运算符初始化C风格的字符串文字吗
- Visual Studio 2019:插入多个C++风格的单行注释
- 如何在本地机器上运行c++和javascript客户端代码(hackerbank风格)
- 重载运算符<<采用谷歌 C++ 风格
- C++许多 SFINAE 风格的过载
- 谷歌风格指南(前向分枝部分)
- 如何使用 cmake pack redhat 风格的 rpm,这是主要的和"-devel"?
- 如何用符合C++核心准则的代码替换C风格的字符串解析
- 如何编写一个类似于kernellist_head的c++风格的双链表实现
- 用现代C++STL数据结构替换旧的C风格数组
- 如何确保 C/C++ 代码中不会缺少 doxygen 风格的文档注释?
- C++(Visual Studio)有Java风格的注释吗?
- 如何"Re-Polish"相同风格的QApplication?
- 如何按照 Google C++风格指南连接字符串文字?
- 如何为特定的构建风格设置 cpp 文件夹
- 使用 rangesv3 在 c++ 中模拟 python 风格的列表理解?
- 包括C++标题,"..."与<...>风格,ISO / IEC官方意见?
- 为什么一个简单的"Hello World"风格的程序不能用Turbo C++编译?
- 我如何使用MFC C 模仿办公风格