简体   繁体   中英

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.

Eg: 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).

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

Also there is the 4th option to use a stateless lambda:

auto free_lmbd = [](void *_ptr) { free (_ptr);};
std::unique_ptr<void, decltype (free_lmbd)> ptr {malloc(10), free_lmbd};

which will also have 8 bytes (at least on my computer), same as your 3rd option.

I recommend reading http://www.bfilipek.com/2016/04/custom-deleters-for-c-smart-pointers.html

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM