Scott Meyers has a good viewpoint on the rule of zero. Basically he advocates default move/copy assign/construct whether or not you actually need them. Basically the general rule of thumb is to avoid the compiler generation of these members, mainly because they are a big source of confusion (I agree with this).
So I was thinking about a good general practice on how to define a class as default movable, copyable, or non-movable, non-copyable. I thought of boost's boost::noncopyable
but I don't like the idea of introducing inheritance for such a functional purpose.
The only thing I can think of that makes sense is to resort to macros. So I came up with something like this:
/// Disable copy construct/assign for the given class T
#define CLASS_NON_COPYABLE(T) \
T(T const&) = delete; \
T& operator=(T const&) = delete
/// Disable move construct/assign for the given class T
#define CLASS_NON_MOVABLE(T) \
T(T&&) = delete; \
T& operator=(T&&) = delete
/// Disable both copy and move construct/assign for the given class T
#define CLASS_NON_COPYABLE_OR_MOVABLE(T) \
CLASS_NON_COPYABLE(T); \
CLASS_NON_MOVABLE(T)
/// Default copy move/assign
#define CLASS_DEFAULT_COPYABLE(T) \
T(T const&) = default; \
T& operator=(T const&) = default
/// Default move construct/assign
#define CLASS_DEFAULT_MOVABLE(T) \
T(T&&) = default; \
T& operator=(T&&) = default
/// Defaulted versions of both copy and move construct/assign for the given class T
#define CLASS_DEFAULT_COPYABLE_OR_MOVABLE(T) \
CLASS_DEFAULT_COPYABLE(T); \
CLASS_DEFAULT_MOVABLE(T)
And an example of how to use them:
class foo
{
public:
foo() = default;
virtual ~foo() = default;
CLASS_NON_COPYABLE(foo);
CLASS_DEFAULT_MOVABLE(foo);
};
int main()
{
foo a, b;
a = b; // FAIL: can't copy; class is "non copyable"
a = foo(); // OK: class is 'default movable'
}
(Live Sample)
To me this looks much cleaner than the alternative:
class foo
{
public:
foo() = default;
virtual ~foo() = default;
foo(foo const&) = delete;
foo(foo&&) = default;
foo& operator=(foo const&) = delete;
foo& operator=(foo&&) = default;
};
This is debatable, as most style-based issues are, but I find benefits in the former:
#ifdef
logic where C++11 isn't available (e.g. CLASS_NON_MOVABLE
would be no-op).Given that I am doing a lot of macro magic here, my soul is in a bit of pain and I feel the need for the obligatory "SO post to see if I'm doing this effectively and not going insane".
So I have a few tightly coupled questions:
Maybe I'm going about this all wrong. Maybe this whole question is self-defeating and I'm over thinking things. I'd be willing to reconceptualize this whole thing too, just in case I've gone too far down the rabbit hole.
For non-union class types (class and struct), the move constructor performs full member-wise move of the object's bases and non-static members, in their initialization order, using direct initialization with an xvalue argument.
The move constructor is not generated because you declared a copy constructor. Remove the private copy constructor and copy assignment. Adding a non-copyable member (like a unique_ptr ) already prevents generation of the copy special members, so there's no need to prevent them manually, anyway.
Copy Constructors is a type of constructor which is used to create a copy of an already existing object of a class type. It is usually of the form X (X&), where X is the class name. The compiler provides a default Copy Constructor to all the classes.
@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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With