Search code examples
c++boostshared-ptr

Custom deleter for shared_ptr that expects an address of pointer


Many C APIs provide release functions that take a **p which besides releasing the resource also sets the pointer to NULL.
I want to wrap such C API calls with a boost::shared_ptr with a custom deleter.

Here's an example with FFMPEG:

AVFrame* frame = av_frame_alloc(); // allocate resource
// Do stuff with frame
av_frame_free(&frame)              // free resource

To leverage RAII, I can rewrite this like so:

AVFrame* frame = av_frame_alloc();
boost::shared_ptr<AVFrame*> frame_releaser(&frame, av_frame_free);
// Do stuff with frame

Note that the shared_ptr<> is of type <AVFrame*> and not <AVFrame> as the pointer type.
This approach requires me to hold the resource and the releaser separately, which has several drawbacks:

  1. frame may be changed externally causing a leak.
  2. It requires 2 variable instead of one, which makes the code more bug prone.

I'd like to use a single shared_ptr variable to both hold the resource and release it when needed.

In the spirit of boost::ref, I'm looking to write or use a generic address_of_arg_wrapper for the deleter that will allow me to write somthing like this:

boost::shared_ptr<AVFrame> frame_handle(av_frame_alloc(), address_of_arg_wrapper(av_frame_free));
// Do stuff with frame_handle.get()

or

boost::shared_ptr<AVFrame> frame_handle(av_frame_alloc(), address_of_arg_wrapper<av_frame_free>());
// Do stuff with frame_handle.get()

It is important that the wrapper be generic and accept any pointer (ref) type, so it can be used with any such API functions.
I also do not want to specify the types.

Does Boost have such a utility?
If not, then how can one write such a generic functor?

EDIT - Solution for completeness:

This solution is based on @R. Martinho Fernandes's answer below.

  1. It includes a template function to create the template functor, so that there's no need to specify the template types.
  2. The code depends on boost::decay. A version that just holds a Fun fun; member also worked for simple cases I tested.
  3. I changed the name to arg_ref_adaptor(). Better name suggestions are welcome!

Here's the code:

#include <boost\type_traits\decay.hpp>

//////////////////////////////////////////////////////////////////////////
// Given a function or callable type 'fun', returns an object with 
// a void operator(P ptr) that calls fun(&ptr)
// Useful for passing C API function as deleters to shared_ptr<> which require ** instead of *.
template <typename Fun>
struct arg_ref_adaptor_functor 
{
public:
   arg_ref_adaptor_functor(Fun fun): fun(fun) {}

   template <typename P> 
   void operator()(P ptr) 
   { fun(&ptr); }

private:
   typename boost::decay<Fun>::type fun;
};

template <typename Fun>
inline arg_ref_adaptor_functor<Fun> arg_ref_adaptor(Fun fun)
{  return arg_ref_adaptor_functor<Fun>(fun); }

Usage:

boost::shared_ptr<AVFrame> frame_handle(::av_frame_alloc() 
                                       ,arg_ref_adaptor(::av_frame_free));
// Do stuff with frame_handle.get()
// The resource will be released using ::av_frame_free() when frame_handle
// goes out of scope.

Solution

  • Setting the pointer to null is pointless since the shared_ptr already guarantees the pointer will never be visible again after destruction. So the code only needs to pass along an address to please the av_frame_free function. I suggest simply writing a function object that passes along the address of its argument.

    template <typename Fun>
    struct address_of_arg_wrapper {
    public:
        address_of_arg_wrapper(Fun fun) : fun(fun) {}
    
        template <typename P>
        void operator()(P ptr) {
            fun(&ptr);
        }
    
    private:
        typename boost::decay<Fun>::type fun;
    };
    

    In C++11 a lambda can be used:

    [](AVFrame* ptr) { av_frame_free(&ptr); }