0

In my code I use buffers currently allocated this way:

char* buf1 = (char*)malloc(size);

However at some points in the code I want to reassign the pointer to some place else in memory. The problem is that there are other places in the code that still need to be able to access the pointer buf1.

What's the best way to do this in C++? Right now I am considering writing a struct with a single char* in it, then allocating an object of this struct type and passing it to the places where I need to, and referring to wrapped pointer to get the current value of buf1.

However it seems that this is similar to what unique_ptr does. If I use unique_ptr how can I wrap a char* with it? I had some trouble with testing this and I'm not sure it's supported.

To clarify: these buffers are bytes of varying sizes.

Jason
  • 531
  • 6
  • 19
  • 5
    And what is this pointer pointing to? `std::string`, `std::vector` and `std::unique_ptr` might all be good solutions, depending on what you need – UnholySheep Apr 22 '19 at 17:37
  • Just a buffer allocated of a given size, see buf1 in the line of code above. I think std::unique_ptr may be a good option, thanks. – Jason Apr 22 '19 at 17:39
  • 3
    `std::unique_ptr` would be the plain rewrite, however you shouldn't use `malloc` with it (or in C++ in general) - that would require you to provide a custom deleter (and has 0 advantages over using the proper C++ way) – UnholySheep Apr 22 '19 at 17:42
  • Is the size fixed? std::vector is normally a good bet for dynamic buffers unless you need to optimise at a low level. – Fire Lancer Apr 22 '19 at 17:44
  • Yes the size is fixed. Do you mean std::vector and then looking up the buffer by its position in the vector? – Jason Apr 22 '19 at 17:51
  • "Best way" is subjective. What are criterias you would judge if it is the best or not? What is the usage of that pointer and/or data? Can you rewrite usage of it everywhere or you still need to pass it as a raw pointer sometimes? To get a good answer you need to ask a good question. – Slava Apr 22 '19 at 17:57

1 Answers1

4

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)…

Michael Kenzel
  • 15,508
  • 2
  • 30
  • 39