Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Memory-efficient custom deleter for std::unique_ptr?

This may be a bit implementation-specific, but some of it seems fundamental.

I'm certain I must be missing something in the standard library.

The problem is this:

I want to implement a std::unique_ptr whose deleter is free()

[ because the value is allocated via malloc() ]

There are, of course, lots of options on how to do this, but (at least in g++ 4.8.4 for x86-64) they appear to have different memory usage implications.

E.g.: Method 1:

std::unique_ptr<char, std::function<void(void*)>> ptr_a(malloc(10), free);

However, sizeof(ptr_a) == 40 bytes (8 for void*, 32 for std::function<>)

Method 2:

std::unique_ptr<void, void (*)(void*)> ptr_b(malloc(10), free);

Somewhat better, as sizeof(ptr_b) == 16 bytes (8 for void*, 8 for bare function pointer ])

Method 3:

template <void (*T)(void*)>
class Caller {
 public:
  void operator()(void* arg) {
    return T(arg);
  }
};
std::unique_ptr<void, Caller<free>> ptr_c(malloc(10));`

At this point, sizeof(ptr_c) == 8 bytes (the minimum possible) - but I've had to introduce a class that's pretty much pure boilerplate (and, as shown, easily templatized).

This seems like such a simple pattern - is there some element in the STL that does what Caller<> does above?

Of course, g++ by default does indeed appear to free() when calling delete on a trivial type - but this seems far from guaranteed by the standard (if nothing else, new/delete may be redefined from the default allocation/deallocation functions, and default_delete would then call the replacement delete).

And plus, there are other cases where the release of some object allocated in a pure-C library would be implemented by a simple function call, rather than a deleter. It seems somewhat tedious to have to wrap such allocation/deallocation functions in classes in order to get std::unique_ptr to call them both correctly and efficiently - which makes me think I'm missing something (most of the rest of the modern C++ specs seem to be extremely well thought out).

like image 844
Kevin Avatar asked Jul 27 '17 04:07

Kevin


People also ask

What is std:: unique_ ptr in c++?

std::unique_ptr is a smart pointer that owns and manages another object through a pointer and disposes of that object when the unique_ptr goes out of scope.

How to transfer ownership unique_ ptr?

In C++11 we can transfer the ownership of an object to another unique_ptr using std::move() . After the ownership transfer, the smart pointer that ceded the ownership becomes null and get() returns nullptr.

How does unique_ ptr work?

A unique_ptr object wraps around a raw pointer and its responsible for its lifetime. When this object is destructed then in its destructor it deletes the associated raw pointer. unique_ptr has its -> and * operator overloaded, so it can be used similar to normal pointer.

What is std :: Default_delete?

std::default_delete is the default destruction policy used by std::unique_ptr when no deleter is specified. Specializations of default_delete are empty classes on typical implementations, and used in the empty base class optimization.


1 Answers

C++11 has an integral_constant type that works on things that aren't integer-like. In C++14, there is a constexpr cast back to the value.

So, in C++14, we can do:

std::unique_ptr<void, std::integral_constant<decltype(free)*, free>> ptr_c(malloc(10));

this is awkward. (This relies on the fact that () will consider cast-to-function-pointer on its left hand side argument).

We can hard code free as:

using default_free = std::integral_constant<decltype(free)*, free>;
std::unique_ptr<void, default_free> ptr_c(malloc(10));

to get rid of some noise at the use site.

In C++17, we can write a helper:

template<auto t>
using val = std::integral_constant< std::decay_t<decltype(t)>, t >;

giving us:

std::unique_ptr<void, val<free>> ptr_c(malloc(10));

which is cleaner in my opinion.

Live examples.

We can write a our own version in C++11:

template<class T, std::decay_t<T> t>
struct val {
  constexpr operator T() noexcept const { return t; }
};
using default_free = val<decltype(free), free>;
std::unique_ptr<void, default_free> ptr_c(malloc(10));
like image 121
Yakk - Adam Nevraumont Avatar answered Oct 17 '22 00:10

Yakk - Adam Nevraumont