Update. One remark in this post about compile-time messages was untrue. As K-ballo has pointed out, declaration =default does not mean that we have a declared and usable member function. Ville Voutilainen also suggested how to fix the situation with a static_assert . This section is now corrected.

Update. I no longer consider the advice given in this post a good one. I left it for reference, but I encourage you to also read this post.

You have probably already heard about the Rule of Zero formulated by R. Martinho Fernandes:

Classes that have custom destructors, copy/move constructors or copy/move assignment operators should deal exclusively with ownership. Other classes should not have custom destructors, copy/move constructors or copy/move assignment operators.

I have run into a certain problem when trying to apply it, and today I wanted to share my observations with you. I noted that Scott Meyers also expressed his concerns about some interpretations of the rule (see here); I came to a similar conclusion, however my problem is somewhat different.

I have decided to follow the rule in my project. I have defined a class that looks more-less like this:

class Tool { private: ResourceA resA_; ResourceB resB_; public: // interface // (no special member functions) };

My colleague is reviewing the code. He asks me a question, ‘does this type have a copy constructor, or a move constructor?’ My response is, ‘since ResourceA has a copy- and a move constructor, and ResourceB has a move constructor but no copy constructor, as a result Tool has a move constructor but no copy constructor.’ I was happy with this response, and with the short class definition. My colleague, in contrast, was a bit confused. This was not as obvious to him. My first thought was ‘apparently he didn’t learn about the Rule of Zero yet’, but upon later reflection I started to share his reservations.

I share the view that a class should explicitly state its interface and separate it from the implementation details. I tend to think (although I know it is a bit of a simplification) that section public indicates the type’s interface, and section private indicates internal implementation details. Of course, in C++ private declarations are not really private: the size of private members affects the size of the class (which is ‘public’); the users of our class see the private members along the public ones; they have to include the headers required by the private members. But still, it is useful to see what is the intended interface of the class and what is an ‘accidental’ interface. It is not uncommon in C++ to provide an interface we never intended. This is because of special member functions that are generated implicitly (possibly with unintended semantics); implicit conversions between scalar types can affect our type’s interface in a surprising way (for examples, see here and here); sometimes by forgetting to put the keyword explicit we make our types implicitly convertible from other unrelated types.

Without an explicit declaration of intent from the author, it is sometimes difficult to say if some portions of the interface are there on purpose, or if they are just an omission. In my example, it is not clear if I intended my type to be movable or not. Can the users rely on it? Since using ResourceA is my implementation detail, at some point I may change this type to ResourceX which is non-movable, and my class Tool will suddenly cease to be movable, with no change in the declared interface.

It is this coupling between the implementation and the public interface that bothers me. I would be more comfortable with an explicit declaration, especially that it has become easier in C++11 to declare the special member functions:

class Tool { private: ResourceA resA_; ResourceB resB_; public: // interface Tool(Tool &&) = default; // noexcept is deduced Tool& operator=(Tool&&) = default; // noexcept is deduced Tool(Tool const&) = delete; Tool& operator=(Tool const&) = delete; };

This is more typing, but this way we are separating the interface from the implementation. If during some future development both resources become (temporarily) copyable, class Tool does not suddenly acquire copy operations.

On the other hand, if one of my resources is changed to something non-movable, the =default declaration will still compile and may give false impression to the clients that they can use a move constructor. But even if we end up with the same problem, it is at least clear what the intention was, and on which side the problem lays. If this is not satisfactory, and you require a compiler error upon inadvertent missing move constructor, you can achieve the effect with yet another declaration:

static_assert(std::is_move_constructible<Tool>::value, "Move constructor could not be generated");

As the result of this exercise, the interface of the class becomes more stable.

For the same reason I would not advise anyone to annotate their functions with noexcept only because their current implementation happens not to throw any exception. The interface is something we commit to for a longer time; longer than till the next change of implementation.

So, am I questioning how the rules for the implicit generation of special members work? Not really: they seem fine for POD-like types, where all the member data are public. In such case the types of data members (and their copyability/movability) contribute to the type’s public interface.

Am I questioning the Rule of Zero? One interpretation thereof. If you read it as “you have to declare no special member functions,” then yes, I can see the advantage in typing the four declarations. But if you read it as “you do not have to provide custom definitions for special member functions,” then the rule is still preserved.

For the end, let me leave you with a question. I have been suggested to use boost::noncopyable instead, in order to declare more briefly that I do not intend my type to be copyable:

class Tool : boost::noncopyable { private: ResourceA resA_; ResourceB resB_; public: // interface // (no special member functions) };

What do you think? Is thus defined class Tool movable?