C++ Smart Pointers – Should Unique_ptr Be Used with Arrays or Vectors?

csmart-pointer

I've been out of C++ for years, last time I used it was back in gamedesign before C++11. I see all these new pointer types which seem great. But I'm unsure when and how to use them. In the old days I would create a buffer like this:

uint bufferSize = 1024;
unsigned char *buffer = (unsigned char*)malloc(bufferSize, sizeof(char));

It's not pretty and I'm pretty sure even back then new/delete was already a thing in C++, but I never learned about them in that time. I don't think raw pointers should be used if avoidable, so I'm trying to get used to writing this now:

uint bufferSize = 1024;
std::unique_ptr<unsigned char[]> buffer;
buffer = std::make_unique<unsigned char[]>(bufferSize);

But this raises the question, should I just map the old raw pointers to unique_ptr like this? There's also std::vector, which seems like a much more natural fit for any array. Should I use a vector instead? Are there rules of thumb for when to use one or the other (for arrays)?

To add some more specific information, I'm making a game and I'm setting up a screenbuffer to draw to, I'm starting with ASCII and will likely move on to pixels somewhere in the future. The buffer size will never have to change at runtime, but it would be nice to be able to play with the screen size a bit during development so that's why I'm not simply declaring a fixed-length array (my bufferSize is actually two consts multiplied; screenWidth and screenHeight). This is just a prototype, it doesn't have to be scalable or deployable, but I still want to use this chance to learn to write better C++.

Best Answer

sizeof(TYPE) has been a bad idea in C and C++ since forever, assuming you could use sizeof *pointer. The problem is that it duplicates the type-Information without adding a check.

In C, casting a void* is also a bad idea, as overriding the language should only be done where and when necessary after careful consideration.

And finally, malloc() only accepts one argument, you want to multiply there instead. Your compiler should warn you though.

Next, this:

uint bufferSize = 1024;
unsigned char *buffer = (unsigned char*)malloc(bufferSize * sizeof(char));

Is not quite equivalent to the following, barring the allocator used and the automatic management of the buffer:

uint bufferSize = 1024;
auto buffer = std::make_unique<unsigned char[]>(bufferSize);

The additional difference is the value-initialization, the latter does it, the former refrains from it.

Is that significant?
We cannot say, it depends on the specific circumstances. Maybe you actually want that initialization, or the compiler can optimize it out.

Or maybe the following matches your needs better:

uint bufferSize = 1024;
std::unique_ptr<unsigned char[]> buffer(new unsigned char[bufferSize]);

Alternative using C++20 std::make_unique_default_init():

uint bufferSize = 1024;
auto buffer = std::make_unique_default_init<unsigned char[]>(bufferSize);

Anyway, unless you use something like directly above, or you have to pass the buffer on to code expecting a newed up buffer, std::vector is likely simpler and is liable to be optimized down to the same code.