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:
- https://akrzemi1.wordpress.com/2015/09/08/special-member-functions/
- 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, andX
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
"Volatile" Qualifier and Compiler Reorderings
Assert That Code Does Not Compile
Openssl::Ssl_Library_Init() Memory Leak
C++ Array Size Dependent on Function Parameter Causes Compile Errors
Or Is Not Valid C++:Why Does This Code Compile
Is Clrscr(); a Function in C++
Opencv Fisheye Calibration Cuts Too Much of the Resulting Image
Why Does Stack<Const String> Not Compile in G++
Why Does 'Std::Basic_Ifstream<Char16_T>' Not Work in C++11
#Error Please Use the /Md Switch for _Afxdll Builds
Why Class Size Depend Only on Data Members and Not on Member Functions
How to Dynamically Allocate Arrays in C++
What Does the "::" Mean in "::Tolower"
Why Does Makeintresource() Work
How Are Objects Stored in Memory in C++