Home > other >  How to use unique_ptr fo automatic memory management?
How to use unique_ptr fo automatic memory management?

Time:09-09

{ char *buf = malloc(n);
  ... realloc(buf,N);
  ...
  free(buf);
}

How to go about using smart pointer protection (against leaking buf) in the above snippet. Replacing first instruction with this :

uBuf = make_unique<char[]>(n); char *buf = uBuf.get();

would "free" me from worrying about the final free() ? Would the unique_ptr tolerate resizing its *raw protégé (no longer n bytes) and do the delete[] expected from it when leaving the scope ?

If not, how to do it ?

CodePudding user response:

would "free" me from worrying about the final free() ?

Yes, it will correctly call delete[] for you.

Would the unique_ptr tolerate resizing its *raw protégé (no longer n bytes) and do the delete[] expected from it when leaving the scope ?

Calling realloc on the raw pointer causes undefined behavior, because it was not allocated with malloc/calloc/realloc, but with new[].

To make this work correctly, you need to use a custom deleter which calls free instead of delete[]. Then you need to allocate the pointer manually instead of using std::make_unique, so that the allocation comes from malloc. Then to handle ownership during the realloc call you need to release the owned pointer, call realloc, check its return value for failure, and then conditionally hand ownership back to the smart pointer, e.g.:

struct FreeDeleter {
    void operator()(void* p) const noexcept { std::free(p); }
};

template<typename T>
// C  20 constraint to assure free is appropriate for the type
requires std::is_trivial_v<std::remove_all_extents_t<T>>
using free_unique_ptr = std::unique_ptr<T, FreeDeleter>;

//...

auto buf = free_unique_ptr<char[]>(static_cast<char*>(std::malloc(n)));

//...

auto buf_raw = buf.release(); // after this `buf` does not own the buffer anymore
if(auto buf_new = std::realloc(buf_raw, N)) {
    buf.reset(static_cast<char*>(buf_new));
} else {
    // realloc failed, return ownership of the old pointer
    buf.reset(buf_raw);
    // handle failure here as appropriate
}

You would probably want to wrap the whole sequence in a function (and the allocation line maybe as well) to avoid mistakes in there. This would be an "unsafe" block where temporarily the smart pointer does not guarantee the deletion (because the pointer is released).


As already mentioned in the comments, this is worth it only if a simple std::vector or std::string doesn't do it. Implementations for these two do not typically use realloc, even for trivial types, as far as I know, but how worth realloc instead of copying to a new allocation immediately is will depend on the allocator used.

  • Related