Is it possible to use object type and free functions as parameters for creating custom deleters for std::unique_ptr
?
I'm new to templates and came up till here:
#include <memory>
template<typename T, typename FreeFunc> struct ObjectDeleter {
const FreeFunc &free_func;
ObjectDeleter(const FreeFunc &free_func) : free_func(free_func){
}
void operator()(T *item)
{
if (item) {
free_func(item);
}
}
};
struct Foo{};
void internal_foo_free(Foo *){}
struct Bar{};
void internal_bar_free(Bar *){}
using unique_foo_ptr =
std::unique_ptr<Foo, ObjectDeleter<Foo>([](Foo *foo){internal_foo_free(foo);});
int main(){
return 0;
}
error:
<source>:19:48: error: wrong number of template arguments (1, should be 2)
19 | std::unique_ptr<Foo, ObjectDeleter<Foo>([](Foo *foo){internal_foo_free(foo);});
| ^
<source>:3:48: note: provided for 'template<class T, class FreeFunc> struct ObjectDeleter'
3 | template<typename T, typename FreeFunc> struct ObjectDeleter {
| ^~~~~~~~~~~~~
<source>:19:50: error: lambda-expression in template-argument only available with '-std=c++2a' or '-std=gnu++2a'
19 | std::unique_ptr<Foo, ObjectDeleter<Foo>([](Foo *foo){internal_foo_free(foo);});
| ^
<source>:19:87: error: template argument 2 is invalid
19 | std::unique_ptr<Foo, ObjectDeleter<Foo>([](Foo *foo){internal_foo_free(foo);});
| ^
Compiler returned: 1
I was suggested to use function pointer (and I extend it to std::function also):
but that adds the possibility of adding throwing statements via the function pointer (or std::function), which the compiler or static analysers won't be able to detect. Parameterising using lambda will make sure that the no-one can add throwing statements in the destructor of std::unique_ptr. This is what I mean by "noexcept-detectable, callable object"
I'm using C++17.
It's complaining because lambdas can't be used as template arguments (before C++20 anyway). Otherwise, lambdas are already callable objects that will not throw unless the function body throws, no wrapper class needed. You just have to do this awkwardness:
auto myDeleter = [&x](Foo* v) { internal_foo_free(v); };
std::unique_ptr<Foo, decltype(myDeleter)> guard { create_foo(), myDeleter };
Originally, I interpreted this question as "I want compilation to fail if someone uses a custom deleter not marked noexcept
". I asked about it, which I think is why you edited your title to include that wording.
noexcept
is a qualifier for optimization hint/documentation purposes. It is totally on the honor system. You can throw right inside them, and your source will still compile (though a static analyzer might complain). If you wanted to enforce that a custom deleter only calls noexcept
functions, in C++17 you can use a static assertion with the noexcept
operator, which returns false if an expression calls a non-noexcept function:
template <auto F>
struct NoExceptDeleter{
template <typename T>
void operator ()(T* arg) const noexcept {
static_assert(noexcept(F(arg)), "deleter must be marked noexcept");
F(arg);
}
};
void good_delete(foo* v) noexcept { free(v); }
std::unique_ptr<foo, NoExceptDeleter<good_delete>> good_guard { make_foo() };
void bad_delete(foo* v) { throw 0; }
std::unique_ptr<foo, NoExceptDeleter<bad_delete>> bad_guard { make_foo() }; // compile-error
Because this takes a function pointer, in C++17 you can only use it with non-capturing lambdas decayed to a function pointer with the +
operator:
auto good_lambda = [](foo* v) noexcept { free(v); }
std::unique_ptr<foo, NoExceptDeleter<+good_lambda>> lambda_guard;
Demo: https://godbolt.org/z/vdEov3
If you needed to capture inside your lambda, you'd have to use a stateful deleter:
template <typename F>
struct StatefulNoExceptDeleter {
F f;
StatefulNoExceptDeleter(F f) : f(f) { }
template <typename T>
void operator ()(T* arg) const noexcept {
static_assert(noexcept(f(arg)), "deleter must be marked noexcept");
f(arg);
}
};
/* ... */
int baz;
auto deleter = [&](Foo* t) noexcept {
std::cout << "labmda: " << baz << std::endl;
delete t;
};
std::unique_ptr<Foo, StatefulNoExceptDeleter<decltype(deleter)>> guard {
new Foo,
StatefulNoExceptDeleter<decltype(deleter)>{ deleter }
};