What is a good way to eliminate the boilerplate involved in defaulting/deleting move/copy semantics?

@HowardHinnant has much better advice for the Rule of Zero:

class foo
{
public:
// just keep your grubby fingers off of the keyboard
};

I chuckled at and upvoted TemplateRex's good answer. That being said, if you have to declare your destructor virtual, well, then you can't just leave everything to the compiler. However by knowing the rules (and assuming a conforming compiler), one can minimize both what you have to type, and what you have to read.

For this specific example I recommend:

class foo
{
public:
    virtual ~foo()        = default;
    foo()                 = default;
    foo(foo&&)            = default;
    foo& operator=(foo&&) = default;
};

Notes:

  • If you declare either move member, both copy members are implicitly deleted. You can use this rule to reduce boiler plate.

  • I recommend putting your data members up at the top of your class, and then your special members directly following that. This is in contrast to many guidelines which recommend putting your data members at the bottom since they are not important for your readers to see. But if your reader wants to know what the special members are going to do when defaulted, the reader needs to see your data members.

  • I recommend always ordering your declared special members in the same order. This helps the (initiated reader) realize when you have not declared a special member. I have a recommended order. But whatever the order, be consistent.

  • The order I recommend is: destructor, default constructor, copy constructor, copy assignment, move constructor, move assignment. I like this order because I consider the destructor the most important special member. That one function tells me a lot about the class design. I like to group my copy members together, and my move members together, because they are often both defaulted, or both deleted.

As far as the copy members go for this example, given the above style guidelines, it is easy (at least for me) to see that they are implicitly deleted, and so there is less to read (grubby fingers and all that :-)).

Here is a brief paper with more rationale for this style of class declaration.

Update

At the risk of going off topic, it is good practice for foo.cpp to contain confirmation that you got the special members right:

static_assert(std::is_nothrow_destructible<foo>{},
              "foo should be noexcept destructible");
static_assert(std::has_virtual_destructor<foo>{},
              "foo should have a virtual destructor");
static_assert(std::is_nothrow_default_constructible<foo>{},
              "foo should be noexcept default constructible");
static_assert(!std::is_copy_constructible<foo>{},
              "foo should not be copy constructible");
static_assert(!std::is_copy_assignable<foo>{},
              "foo should not be copy assignable");
static_assert(std::is_nothrow_move_constructible<foo>{},
              "foo should be noexcept move constructible");
static_assert(std::is_nothrow_move_assignable<foo>{},
              "foo should be noexcept move assignable");

I've added "nothrow" in some places. Remove it if applicable, or add it to more places if applicable. Instead use "trivially" if applicable. One size doesn't fit all.

This combination of saying what you intend in the header, and confirming what you said is what you got in the source, is very conducive to correct code.

Hiding this "boiler-plate" under macros has a cost: The reader has to look up the definition of the macro. If you use such a macro, judge if the macro's benefit outweighs its cost.