In general, this question cannot be answered. There are simply way too many things you could be wanting to be doing with an array of char
. Without knowing what it actually is that you want to do, its impossible to say what may be good abstractions to use…
If you want to do stuff with strings, just use std::string
. If you want a dynamically-sized buffer that can grow and shrink, use std::vector
.
If you just need a byte buffer the size of which is determined at runtime or which you'd just generally want to live in dynamic storage, I'd go with std::unique_ptr
. While std::unique_ptr<T>
is just for single objects, the partial specialization std::unique_ptr<T[]>
can be used for dealing with dynamically allocated arrays. For example:
auto buffer = std::unique_ptr<char[]> { new char[size] };
Typically, the recommended way to create an object via new
and get an std::unique_ptr
to it would be to use std::make_unique. And if you want your buffer initialized to some particular value, you should indeed use std::make_unique<char[]>(value)
. However, std::make_unique<T[]>()
will value-initialize the elements of the array it creates. In the case of a char
array, that effectively means that your array will be zero-initialized. In my experience, compilers are, unfortunately, unable to optimize away the zero-initialization, even if the entire buffer would be overwritten first thing right after being created. So if you want an uninitialized buffer for the sake of avoiding the overhead of initialization, you can't use std::make_unique
. Ideally, you'd just define your own function to create a default-initialized array via new
and get an std::unique_ptr
to it, for example:
template <typename T>
inline std::enable_if_t<std::is_array_v<T> && (std::extent_v<T> == 0), std::unique_ptr<T>> make_unique_default(std::size_t size)
{
return std::unique_ptr<T> { new std::remove_extent_t<T>[size] };
}
and then
auto buffer = make_unique_default<char[]>(new char[size]);
It seems that C++20 will include this functionality in the form of std::make_unique_default_init
. So that would be the preferred method then.
Note that, if you're dealing with plain std::unique_ptr
, you will still have to pass around the size of the buffer separately. You may want to bundle up an std::unique_ptr
and an std::size_t
if you're planning to pass around the buffer
template <typename T>
struct buffer_t
{
std::unique_ptr<T[]> data;
std::size_t size;
};
Note that something like above struct represents ownership of the buffer. So you'd want to use this, e.g., when returning a new buffer from a factory function, e.g.,
buffer_t makeMeABuffer();
or handing off ownership of the buffer to someone else, e.g.,
DataSink(buffer_t&& buffer)
You would not want to use it just to point some function to the buffer data and size do some processing without transferring ownership. For that, you'd just pass a pointer and size, or, e.g., use a span (starting, again, with C++20; also available as part of GSL)…