Reputation: 2066
I'm trying to use std::unique_ptr
with a custom deleter to simplify managing the life of handles returned to me from various C APIs. This is nice in theory, but I'm struggling to find an approach which is both optimal at runtime, and doesn't have a heap of boilerplate for every wrapped type.
For example, consider some opaque type foo
which must be freed by passing its pointer to destroy_foo
:
// approach 1: pass destroy_foo at runtime (bad for performance)
using foo_ptr = std::unique_ptr<foo, decltype(&destroy_foo)>;
foo_ptr bar{create_foo(...), destroy_foo};
// approach 2: make a deleter type (verbose - for many types)
struct foo_deleter
{
void operator()(foo* p)
{
destroy_foo(p);
}
};
using foo_ptr = std::unique_ptr<foo, foo_deleter>;
foo_ptr bar{create_foo(...)};
The first approach is hard for the compiler to optimize because I'm passing a function pointer around, so it's out. The second approach seems needlessly verbose. I have quite a lot of types like this I want to manage, and manually creating a class for each one is painful.
How can I define a class template that takes destroy_foo
and gives me a type equivalent to foo_deleter
? Or is there a standard library template to do this?
// best of both worlds - to_obj<Func> makes foo_deleter from destroy_foo...
using foo_ptr = std::unique_ptr<foo, to_obj<destroy_foo>>;
foo_ptr bar{create_foo(..)};
So given any function, the template would define a class with an operator()
which simply forwards all arguments to the function, and returns the result.
Upvotes: 5
Views: 2198
Reputation: 5267
What you need to do is use the facilities of c++11 function adaptors std::bind
or std::mem_fn
, which convert a function to a function object.
Here's a demonstration.
#include <iostream>
#include <string>
#include <functional>
#include <typeinfo>
int myFun( const std::string& s, int i )
{
std::cout << s << '\n';
return i * i;
}
template<typename Func, typename... TArgs>
decltype(auto) toFunctor( Func f, TArgs&&... args )
{
return std::bind( f, std::forward<TArgs>( args )... );
}
int main()
{
using namespace std::string_literals;
std::cout << "Func\n"s;
std::cout << typeid( myFun( "hello"s, 5 ) ).name() << '\n';
std::cout << "Function object\n"s;
auto f = toFunctor( myFun, "hello"s, 5 );
std::cout << typeid( f ).name() << '\n';
f();
}
Output:
Func
int
Function object
class std::_Binder<struct std::_Unforced,int (__cdecl*& __ptr64)(class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > const & __ptr64,int),class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,int>
hello
Upvotes: 0
Reputation: 1670
You can also use lambdas as a short hand syntax for creating the deleter functor.
auto foo_deleter = [](foo *ptr){ destroy_foo(ptr); };
std::unique_ptr<foo, decltype(foo_deleter)> foo_ptr(create_foo(), foo_deleter);
static_assert(sizeof(foo_ptr) == sizeof(void *), "No size overhead");
In C++20 you can leave out the deleter parameter, making it more obvious that it is not stored.
Yet another idea is to overload std::default_delete<> for foo:
// In header:
template<>
struct std::default_delete<foo> {
void operator()(foo *f) const { destroy_foo(f); };
};
// Usage:
std::unique_ptr<foo> foo_ptr(create_foo());
static_assert(sizeof(foo_ptr) == sizeof(void *), "No size overhead");
But this is probably a bad idea and surprising to readers. Make sure the overloaded std::default_delete
is in scope, or it will use the standard delete.. Also, you can no longer create a smart pointer to a foo type that is managing heap memory.
Upvotes: 0
Reputation: 1376
In C++17:
template <auto F>
struct Functor
{
template <typename... Args>
auto operator()(Args&&... args) const { return std::invoke(F, std::forward<Args>(args)...); }
};
allows:
std::unique_ptr<char, Functor<printf>>(new char[50]{ "Hello Template Metaprogramming World!" });
Upvotes: 2
Reputation: 120021
Something like
template<typename T, void (*func)(T*)>
struct Deleter{
void operator()(T* t) { func(t); }
};
??
Or if you want something a bit more robust
template <typename t>
struct function_traits;
template <typename R, typename A>
struct function_traits<R (*)(A)>
{
using t_ret = R;
using t_arg = A;
};
template <typename F, F* f>
struct Functor
{
using FT = function_traits<F*>;
typename FT::t_ret operator()(typename FT::t_arg a) {
return f(a);
}
};
void mydeleter(int*);
#define FUNCTOR(F) Functor<decltype(F),&F>
Or using the full might of C++11
template <typename F, F* f>
struct Functor
{
template<typename... A>
auto operator()(A&&... a) -> decltype(f(std::forward<A>(a)...)) {
return f(std::forward<A>(a)...);
}
};
#define FUNCTOR(F) Functor<decltype(F),&F>
Upvotes: 5
Reputation: 3826
If I understand your question correctly, you're looking for something like this?
#include <iostream>
#include <memory>
template <typename TYPE>
void custom_deletor(TYPE * t)
{
delete t;
}
template <typename TYPE>
struct delete_functor
{
void operator () (TYPE * o)
{
custom_deletor(o);
}
};
template <typename TYPE>
std::unique_ptr<TYPE, delete_functor<TYPE>> make_unique(TYPE * data)
{
return std::unique_ptr<TYPE, delete_functor<TYPE>>(data, delete_functor<TYPE>());
}
struct foo
{
static void special_delete(foo * bar)
{
printf("BYE BYE \n");
delete bar;
}
};
template <> void custom_deletor<foo>(foo * bar)
{
foo::special_delete(bar);
}
int main(int argc, const char * argv[])
{
auto v = make_unique(new foo);
return 0;
}
Upvotes: 0