c++c++11rule-of-zero

unique_ptr, custom deleter, and Rule of Zero


I am writing a class that uses two objects created using a C interface. The objects look like:

typedef struct... foo_t;
foo_t* create_foo(int, double, whatever );
void delete_foo(foo_t* );

(similarly for bar_t). Because C++11, I want to wrap these in a smart pointer so I don't have to write any of the special methods. The class will have unique ownership of the two objects, so unique_ptr logically make sense... but I would still have to write a constructor:

template <typename T>
using unique_ptr_deleter = std::unique_ptr<T, void(*)(T*)>;

struct MyClass {
     unique_ptr_deleter<foo_t> foo_;
     unique_ptr_deleter<bar_t> bar_;

     MyClass()
         : foo_{nullptr, delete_foo}
         , bar_{nullptr, delete_bar}
     { }

     ~MyClass() = default;

     void create(int x, double y, whatever z) {
         foo_.reset(create_foo(x, y, z));
         bar_.reset(create_bar(x, y, z));
};

On the flip side, with shared_ptr, I wouldn't have to write a constructor, or use a type alias, since I could just pass in delete_foo into reset() - although that would make my MyClass copyable and I don't want that.

What is the correct way to write MyClass using unique_ptr semantics and still adhere to Rule of Zero?


Solution

  • Your class doesn't need to declare a destructor (it will get the correct default implementation whether or not you declare it defaulted), so still obeys the "Rule of Zero".

    However, you might improve this by making the deleters function objects, rather than pointers:

    template <typename T> struct deleter;
    template <> struct deleter<foo_t> {
        void operator()(foo_t * foo){delete_foo(foo);}
    };
    template <> struct deleter<bar_t> {
        void operator()(bar_t * bar){delete_bar(bar);}
    };
    
    template <typename T>
    using unique_ptr_deleter = std::unique_ptr<T, deleter<T>>;
    

    This has a few benefits: