How can I safely overload custom deleter of std::unique_ptr?

Viewed 164

I am trying to reduce code duplication while using std::unique_ptr and it's custom deleter.

I have the some containers Foo, Bar which are allocated using one custom allocator, so cannot be freed with delete.

So the current code is:

struct UniqueFooDeleter
{
   void operator()(Foo* foo) 
   {
      internal_free(foo);
   }
};
using unique_foo_ptr = std::unique_ptr<Foo, UniqueFooDeleter>;

struct UniqueBarDeleter 
{
   void operator()(Bar* bar) 
   {
      internal_free(bar);
   }
};
using unique_bar_ptr = std::unique_ptr<Bar, UniqueBarDeleter>;

I changed it to:

struct UniqueInternalDeleter
{
   void operator()(Bar* bar)
   {
      internal_free(bar);
   }

   void operator()(Foo* foo)
   {
      internal_free(foo);
   }
};
using unique_bar_ptr = std::unique_ptr<Bar, UniqueInternalDeleter>;
using unique_foo_ptr = std::unique_ptr<Foo, UniqueInternalDeleter>;

How can I do better such that any number of containers that are allocated via internal_free can be used as std::unique_ptrs ?

2 Answers

You can make the UniqueInternalDeleter as templated functor and static_assert, if the T is not Foo or bar.

#include <type_traits> // std::is_same_v

template<typename T>
struct UniqueInternalDeleter /* final */
{
   static_assert(std::is_same_v<T, Foo> || std::is_same_v<T, Bar>,
      " T must be either Foo or Bar");

   void operator()(T* barOrfoo)
   {
      internal_free(barOrfoo);
   }

private:
   void internal_free(T* barOrfoo)
   {
      if constexpr(std::is_same_v<T, Foo>)
         // code for `Foo*`
      else
         // code for `Bar*`

   }
};

This makes the your alias to be more specific for Bar and Foo:

using unique_bar_ptr = std::unique_ptr<Bar, UniqueInternalDeleter<Bar>>;
using unique_foo_ptr = std::unique_ptr<Foo, UniqueInternalDeleter<Foo>>;

One possible approach:

template<class> struct needs_internal_free : std::false_type { };

struct unique_internal_deleter {
    template<class T>
    void operator()(T* ptr) const {
        static_assert(needs_internal_free<T>::value);
        internal_free(ptr);
    }
};

template<class T>
using unique_internal_ptr = std::unique_ptr<T, unique_internal_deleter>;

Now we can declare specific types:

template<> struct needs_internal_free<Foo> : std::true_type { };
using unique_foo_ptr = unique_internal_ptr<Foo>;

template<> struct needs_internal_free<Bar> : std::true_type { };
using unique_bar_ptr = unique_internal_ptr<Bar>;
Related