Question

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.

Was it helpful?

Solution

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>

OTHER TIPS

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!" });

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

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.

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
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top