0

I need a smart buffer that uses malloc and free such as for example:

shared_ptr<byte[]> buffer((byte*) malloc(123), free) 
unique_ptr<byte[], decltype(&free)> buffer((byte*) malloc(123), free);

However I find it tedious and error-prone to separately keep track of the size of the buffer.

The purpose of this smart array pointer is to store a buffer of binary data inside a class, which can be passed around to other functions, and which could later be release()ed and passed to a C library which will handle the memory and later call free(). I need to accomplish this without copying the buffer.

I could wrap a unique_ptr and a size variable in a struct but that seems quite inelegant. Getting the pointer would then be static_vector.arr.get(), accessing an element of the array would be static_vector.arr[i] which I don't like at all. I guess I could define the [] operator and the get() function. I also want to be able to call release() so I'd end up making a decent sized wrapper for unique_ptr when I literally just want a unique_ptr with one little field to keep track of the size (why isn't it there in the first place?)

Is it possible to create a class which inherits std::unique_ptr<byte[]> and adds a size field?

  • 1
    Use [`std::span`](https://en.cppreference.com/w/cpp/container/span) to wrap the pointer + size? – NathanOliver Mar 08 '23 at 21:03
  • @NathanOliver but then I need to always manually manage the memory. If I put `free()` in the destructor of the class which stores the buffer then if I pass the buffer into the C library and then the instance dies the buffer will also die. If I don't put `free()` in the destructor then I will forget to call `free()`. – Dylan Bradshaw Mar 08 '23 at 21:06
  • 3
    You seem to be describing a `std::vector`. – Drew Dormann Mar 08 '23 at 21:08
  • I though you said you said you passing this pointer to a C API and it would free it when it was done with it? Is that not the case? Are you responsible for managing the memory? – NathanOliver Mar 08 '23 at 21:08
  • 3
    @DylanBradshaw Sounds like you rather want a `std::vector`. – πάντα ῥεῖ Mar 08 '23 at 21:09
  • @NathanOliver I want to be able to pass it to the C library but that's not the only purpose of the buffer. – Dylan Bradshaw Mar 08 '23 at 21:09
  • I can't use `std::vector` because the C library will call `free()` on the vectors underlying buffer if the vector hasn't already died and deallocated my buffer before it can be used. – Dylan Bradshaw Mar 08 '23 at 21:10
  • 2
    If the C function is going to call `free`, you can't use any C++ data structures at all. You'll be forced to use a dumb pointer allocated with `malloc`. – Mark Ransom Mar 08 '23 at 21:11
  • @MarkRansom what's wrong with a unique_ptr and a custom deleter? Simply call `release()` when you want the C library to take over handling the memory. – Dylan Bradshaw Mar 08 '23 at 21:13
  • 3
    The `free` will invalidate the pointer held by the unique_ptr and you'll get a double delete. The whole purpose of smart pointers is to manage the lifetime of the pointer, if the pointer is already being managed another way you're just asking for trouble. – Mark Ransom Mar 08 '23 at 21:15
  • 1
    @DylanBradshaw: I'm not an expert on custom deleters, but if at all, the custom deleter should not be `free()` but `donothing()`. Because `free()` is already called by the C function and should not be called again by the custom deleter. Problem is: what if someone creates such a data structure and never calls a C function? – Thomas Weller Mar 08 '23 at 21:15
  • 1
    Looks like you are going to have to write your own wrapper then if you want to selectively manage the memory while also keeping the size tied to the pointer. – NathanOliver Mar 08 '23 at 21:15
  • 1
    90% of the time the smart pointer will free the buffer, but I want the capability to `release()` the buffer and let the C library be responsible for freeing it. – Dylan Bradshaw Mar 08 '23 at 21:20
  • Implementing `shared_ptr` and `unique_ptr` isn't so difficult (at least a basic version of it). Maybe you should write your own `cfree_ptr` through which you can also call the C function. If the C function was called, you do nothing and if it wasn't called, you can free the memory. – Thomas Weller Mar 08 '23 at 21:20
  • @ThomasWeller it really looks more and more like a custom class is needed. Unfortunately there's no good way to know if the C function freed the memory or not, so there's no way to make this really foolproof. And that seems to be the point of this exercise, to make it foolproof. – Mark Ransom Mar 08 '23 at 21:33
  • I do not understand what is the exact problem. So just write your own class that has the properties that you need. `Is it possible to create a class which inherits std::unique_ptr and adds a size field?` It is, but it sounds more clear to me to make std::unique_ptr a member instead, composition is simpler to manage over inheritance.. – KamilCuk Mar 08 '23 at 21:49
  • 1
    @DylanBradshaw "*90% of the time the smart pointer will free the buffer, but I want the capability to `release()` the buffer and let the C library be responsible for freeing it.*" - since the C API will be freeing the memory, simply use the C API's provided freeing function as the smart pointer's `deleter`. – Remy Lebeau Mar 08 '23 at 22:02
  • It tends to be frowned upon, but since you want something that essentially _is a_ `std::unique_ptr` and a `std::span` you could [inherit from both](https://godbolt.org/z/oW9K75eqK). It's perhaps a bit wasteful since it would store the same pointer twice and I'm sure there's some way you could convince them to get out of sync, but it could be made to work. – Miles Budnek Mar 08 '23 at 22:57
  • @MilesBudnek Woah, yeah that's cool. – Dylan Bradshaw Mar 08 '23 at 23:47
  • If `std::unique_ptr` can have special member functions such as the `[]` operator only for when it is an array type, why can it also not have a `size` field? – Dylan Bradshaw Mar 08 '23 at 23:58
  • `std::unique_ptr` doesn't have a `size` field because it doesn't *need* one. One of the guiding principles of C++ is that you never pay for anything you don't need. – Mark Ransom Mar 09 '23 at 03:08

1 Answers1

3

unique_ptr<byte[], decltype(&free)> buffer((byte*) malloc(123), free);

However I find it tedious and error-prone to separately keep track of the size of the buffer.

So keep it together.

#include <cstdlib>
#include <memory>

template<size_t N>
struct MyArray {
    std::unique_ptr<std::byte[], decltype(free)*> buffer{
            reinterpret_cast<std::byte*>(malloc(N)),
            free};
    constexpr static size_t size = N;
};
KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • Yeah this would work but then when I want to access the buffer I have to use `myArray.buffer.get()` and when I want to release the buffer I have do `myArray.buffer.release()` which leaves me with a useless myArray and looks kind of disgusting. I'd much rather have a unique_array_ptr. – Dylan Bradshaw Mar 08 '23 at 22:24
  • 1
    I do not understand. So add get() and release() method? – KamilCuk Mar 08 '23 at 22:24
  • Yeah that's better but it seems lazy to only implement the methods I want right now. I feel like I should define all methods and operators of `std::unique_ptr` and simply forward them. This may be the best solution, I just wish there was a `std::unique_array_ptr` which was identical to `std::unique_ptr` but with a `size` field. Thanks for your help. – Dylan Bradshaw Mar 08 '23 at 22:50