Special member functions

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?

This entry was posted in programming and tagged . Bookmark the permalink.

10 Responses to Special member functions

  1. jzurkowski says:

    boost::noncopyable – that’s bit incomplete. But what about introducing two macros to make things self-explanatory: DELETED_COPYABILITY(class) and DEFAULT_MOVABILITY(class)?

  2. viboes says:

    I guess it is not movable neither as the default move constructor will not be generated any more, isn’t it?

  3. gnzlbg says:

    > Not really: they seem fine for POD-like types, where all the member data are public.

    Providing a constructor _in any way_ (even with = default) prevents a type from becoming an aggregate.

    So even tho 99% of the time = default; is the same as doing nothing, this is C++, where there is always a 1% of the time where any rule isn’t true.

  4. kaballo says:

    Since `=default` can mean `=delete`, seeing a defaulted special member function may convey intent but it does not specify an interface. Whether or not the `Tool` class has a non-deleted move constructor/assignment-operator still depends on the implementation internals.

    • So, you are saying that I have typed `=default`, the program compiles fine I may feel I have the move constructor, but the clients will still get a compile-time error.

      Indeed, this is unfortunate, and my post inaccurate; I’ll fix that. Thanks. In fact, I find it quite surprising.

      But I still find an added value in declaring the constructor this way: it is clear who is responsible for the compile-time failure, and where the problem is to be corrected.

  5. Technically, boost::noncopyable is also nonmovable, since declaring any copy operation (as private or deleted) will prevent the compiler to generate move operations. An attempt to generate move operations for derived classes will therefore fall back to try to use the forbidden copy operations in noncopyable.

    That said, I agree with your point that it is not clear anymore from the name what “noncopyable” means. And renaming it to “neithercopyablenormovable” sounds a bit clumsy, though for completeness there could be a “movablebutnoncopyable” as baseclass for your example.

    I have written about a “rule of all or nothing” some time ago (http://buff.ly/1LjFTBo) :

    RULE OF ALL OR NOTHING: A class that needs to declare one or more of the destructor, copy/move constructor or copy/move assignment operations should explicitly default the rest of those operations. All other classes should not declare any destructor, copy/move constructor or copy/move assignment operator.

    This wording would fit to the case you describe in the sense that if the interface is not obvious there is a need to declare the members to clarify it, although I did not think about cases of unclear interfaces back then.

  6. Pingback: Why does destructor disable generation of implicit move methods? – w3toppers.com

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.