Why Does Destructor Disable Generation of Implicit Move Methods

Why does destructor disable generation of implicit move methods?

"The Rule of Zero" is in fact about something else than what special member functions are generated and when. It is about a certain attitude to class design. It encourages you to answer a question:

Does my class manage resources?

If so, each resource should be moved to its dedicated class, so that your classes only manage resources (and do nothing else) or only accumulate other classes and/or perform same logical tasks (but do not manage resources).

It is a special case of a more general Single Responsibility Principle.

When you apply it, you will immediately see that for resource-managing classes you will have to define manually move constructor, move assignment and destructor (rarely will you need the copy operations). And for the non-resource classes, you do not need to (and in fact you probably shouldn't) declare any of: move ctor/assignment, copy ctor/assignment, destructor.

Hence the "zero" in the name: when you separate classes to resource-managing and others, in the "others" you need to provide zero special member functions (they will be correctly auto-generated.

There are rules in C++ what definition (of a special member function) inhibits what other definitions, but they only distract you from understanding the core of the Rule of Zero.

For more information, see:

  1. https://akrzemi1.wordpress.com/2015/09/08/special-member-functions/
  2. https://akrzemi1.wordpress.com/2015/09/11/declaring-the-move-constructor/

Does a default virtual destructor prevent compiler-generated move operations?

Yes, declaring any destructor will prevent the implicit-declaration of the move constructor.

N3337 [class.copy]/9: If the definition of a class X does not explicitly declare a move constructor, one will be implicitly declared
as defaulted if and only if

  • X does not have a user-declared copy constructor,
  • X does not have a user-declared copy assignment operator,
  • X does not have a user-declared move assignment operator,
  • X does not have a user-declared destructor, and
  • the move constructor would not be implicitly defined as deleted.

Declaring the destructor and defining it as default counts as user-declared.

You'll need to declare the move constructor and define it as default yourself:

WidgetBase(WidgetBase&&) = default;

Note that this will in turn define the copy constructor as delete, so you'll need to default that one too:

WidgetBase(const WidgetBase&) = default;

The rules for copy and move assignment operators are pretty similar as well, so you'll have to default them if you want them.

Does forbidding copy operations automatically forbid move operations?

MSVC conforms to the standard in this case. [class.copy]/9 in C++14 reads:

If the definition of a class X does not explicitly declare a move constructor, one will be implicitly declared
as defaulted if and only if

  • X does not have a user-declared copy constructor,
  • X does not have a user-declared copy assignment operator,
  • X does not have a user-declared move assignment operator, and
  • X does not have a user-declared destructor.

So your class has no move constructor and any attempt to move it will fall back to the deleted copy constructor.

Why does the insertion of user defined destructor require an user defined copy constructor

The code gets compiled because buildShip() would use the move constructor automatically generated by the compiler when returning tmp. Adding user-declared destructor prevents the compiler from auto-generating one. E.g., see this or this questions. And the compiler-generated copy constructor can not be used because of the member up which is std::unique_ptr. And copy constuctor of unique_ptr is explicitly deleted.

So this will compile, because the compiler is explicitly asked to generate the move constructor:

class Ship
{
public:
Ship(){}
Ship(Ship&&) = default;
~Ship(){}
std::unique_ptr<container> up;
};

C++ declare a move/copy operation will suppress generation of related operations?

I define a move assignment, there should be not default copy assignment generated as said in the book

Correct.

but how could a3 gets the copy of a1?

It couldn't according to the standard. If the compiler does not give you a diagnostic message for this, then the compiler doesn't conform to the standard.


The result of a1+a2 is a rvalue

Correct.

how could this rvalue be passed to copy assignment whose argument is const A&?

Because rvalues can be bound to lvalue references to const. The lifetime of the temporary object is extended to match the potential lifetime of the reference. That is for the duration of the function in the case of a reference argument.

just add destructor that do nothing can cause compile error (around std::move), why?

The implicitly-declared move constructor is only present if a class does not have a user-declared destructor. Therefore the answer to 2. is YES.

The answer to 1. is that this is hard rule and can be found in 12.8, paragraph 9 of the standard:

If the definition of a class X does not explicitly declare a move constructor, one will be implicitly declared
as defaulted if and only if

  • X does not have a user-declared copy constructor,
  • X does not have a user-declared copy assignment operator,
  • X does not have a user-declared move assignment operator,
  • X does not have a user-declared destructor, and
  • the move constructor would not be implicitly defined as deleted.

[ Note: When the move constructor is not implicitly declared or explicitly supplied, expressions that
otherwise would have invoked the move constructor may instead invoke a copy constructor. — end note ]

The best way of getting this to run,is by using something like a smart pointer, i.e., a base class or member that does define all five special members (and very little else) so that you don't have to. In this case, an integer handle equivalent to std::unique_pointer should work well. However, keep in mind that databases, like files, can have errors while closing, so standard non-throwing destructor semantics don't cover all cases.



Related Topics



Leave a reply



Submit