unique_ptr, custom deleter, and Rule of Zero
Asked Answered
A

1

8

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?

Apologetic answered 13/3, 2015 at 15:9 Comment(0)
A
9

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:

  • the unique_ptr doesn't need to store an extra pointer
  • the delete function can be called directly, rather than via a pointer
  • you don't need to write a constructor; the default constructor will do the right thing.
Alumnus answered 13/3, 2015 at 15:21 Comment(2)
This looks like something that should be in the standard library.Modie
This was great. I ended up doing a slight variation of this, with template <typename T, void (*Deleter)(T*)> struct unique_ptr_deleter; which has a private class deleter and then using type = std::unique_ptr<T, deleter>;. Saves the amount of typing necessary... then I can stick both in together (unique_ptr_deleter_t<foo_t, delete_foo> foo_;)Apologetic

© 2022 - 2024 — McMap. All rights reserved.