Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Modern C++ idiom for allocating / deallocating an I/O buffer

For I/O work, I need to read N bytes into a buffer. N is known at run time (not compile time). The buffer size will never change. The buffer is passed to other routines to compress, encrypt, etc: it's just a sequence of bytes, nothing higher than that.

In C, I would allocate the buffer with malloc and then free it when I'm done. However, my code is modern C++, certainly no mallocs in place, and very few raw new and delete: I'm making heavy use of RAII and shared_ptr. None of those techniques seem appropriate for this buffer, however. It's just a fixed length buffer of bytes, to receive I/O and make its contents available.

Is there a modern C++ idiom to this elegantly? Or, for this aspect, should I just stick with good ol' malloc?

like image 658
SRobertJames Avatar asked Jun 02 '15 11:06

SRobertJames


People also ask

What is buffer allocation?

The buffer allocation model of NvSciBuf is summarized as follows: If two or more hardware engines wants to access a common buffer (e.g., one engine is writing data into the buffer and the other engine is reading from the buffer), then: 1. Applications create an attribute list for each accessor.

Why do we allocate memory?

Allocating memory helps the operating system know which applications need more memory and give it only to who need it.

Does Read allocate memory?

No, read does not do any allocation. It reads data into the buffer you provide as an argument, which must have been allocated by you prior to calling read. You can use a buffer on the heap or stack, or a global buffer; its your choice.


2 Answers

Basically, you have two main C++-way choices:

  • std::vector
  • std::unique_ptr

I'd prefer the second, since you don't need all the automatic resizing stuff in std::vector, and you don't need a container - you need just a buffer.

std::unique_ptr has a specialization for dynamic arrays: std::unique_ptr<int[]> will call delete [] in it's destructor, and will provide you with appropriate operator [].

If you want the code:

std::unique_ptr<char[]> buffer(new char [size]); some_io_function(buffer.get(), size); // get() returnes raw pointer 

Unfortunatelly, it doesn't have a way to retrieve the size of the buffer, so you'll have to store it in a variable. If it confuses you, then std::vector will do the work:

std::vector<char> buffer(size); some_io_function(buffer.data(), buffer.size()); // data() returnes raw pointer 

If you want to pass the buffer around, it depends on how exactly you do it.

Consider the following case: the buffer is filled somewhere, then processed somewhere else, stored for some time, then written somewhere and destroyed. It happens that you never really need two places in the code to own the buffer, and you can simply std::move it from place to place. For this use case, std::unique_ptr will work perfectly, and will protect you from occasionally copying the buffer (while with std::vector you can copy it by mistake, and no error or warning will arise).

If, conversely, you need several places in the code to hold the same buffer (maybe it is filled / used / processed in more then one place simultaneously), you definitely need std::shared_ptr. Unfortunately, it does not have array-like specialization, so you'll have to pass appropriate deleter:

std::shared_ptr<char> buffer(new char[size], std::default_delete<char[]>()); 

The third option is if you really need to copy the buffer. Then, std::vector will be simpler. But, as I've already mentioned, I feel that it is not the best way. Also, you can always copy the buffer hold by std::unique_ptr or std::shared_ptr manually, which clearly documents your intention:

std::uniqure_ptr<char[]> buffer_copy(new char[size]); std::copy(buffer.get(), buffer.get() + size, buffer_copy.get()); 
like image 184
lisyarus Avatar answered Sep 21 '22 17:09

lisyarus


In C++14, there's a very syntactically clean way of achieving what you want:

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 

Note that the above construct will value-initialize (zero out) the buffer. If you want to skip the initialization to save a few cycles, you'll have to use the slightly uglier form given by lisyarus:

std::unique_ptr<uint8_t[]> buf_ptr(new uint8_t[n]); 

C++20 introduces std::make_unique_for_overwrite, which allows the non-initializing line above to be written more concisely as:

auto buf_ptr = std::make_unique_for_overwrite<uint8_t[]>(n); 
like image 21
Matt Whitlock Avatar answered Sep 23 '22 17:09

Matt Whitlock