The rule that the compiler shouldn't synthesize move operations for a class that declares a destructor or a copy operation (i.e. copy constructor/assignment) makes sense. Afterall, by declaring these operations, the class admits that it needs to do some custom bookkeeping.
However, this reasoning does not apply when the class defines the destructor or a copy operation as =default
? Shouldn't then this case be an exception from the rule?
EDIT: One reason I might want to define the destructor as =default
, but not the other special operations, is when I need a virtual destructor for the base class, so I am forced to define one to make it virtual.
The default destructor calls the destructors of the base class and members of the derived class. The destructors of base classes and members are called in the reverse order of the completion of their constructor: The destructor for a class object is called before destructors for members and bases are called.
If a copy constructor, copy-assignment operator, move constructor, move-assignment operator, or destructor is explicitly declared, then: No move constructor is automatically generated. No move-assignment operator is automatically generated.
A move constructor enables the resources owned by an rvalue object to be moved into an lvalue without copying. For more information about move semantics, see Rvalue Reference Declarator: &&.
In C++, the compiler creates a default constructor if we don't define our own constructor. In C++, compiler created default constructor has an empty body, i.e., it doesn't assign default values to data members. However, in Java default constructors assign default values.
N3174 seems to have been the proposal that introduced the rule user-declared dtor/copy op => no implicit move. In this paper, Bjarne Stroustrup shows concern for certain class invariants, as in:
class Vec { vector<int> v; int my_int; // the index of my favorite int stored in v // implicit/generated copy, move, and destructor // ... };
Vec
has what I call an implicit invariant: There is a relation between two objects (here, the members) that is nowhere stated in declaratively in the code.
Note that this invariant is broken by move operations, which is especially evil if they're implicitly generated, and change the meaning of code that was formerly well-behaved (in C++03 where rvalues are copied).
Because of these invariants, Stroustrup proposed:
- Move and copy are generated by default (if an only if their elements move or copy as currently specified in the FCD [dyp: FCD = final committee draft])
- If any move, copy, or destructor is explicitly specified (declared, defined,
=default
, or=delete
) by the user, no copy or move is generated by default.
(Later, he suggests to only deprecate implicit generation of copy in 2., not remove it entirely in C++11)
The best explanation I can find in N3174 why defaulted user-declared operations are included in the second bullet point is the relation between invariants and special member functions:
I think that the most worrying cases are equivalent to
Vec
. For such classes there is an implicit invariant but no “indication” to help the compiler [dyp: discover the existence of an invariant] in the form of a user-specified copy constructor. This kind of example occurs (occurred) when a programmer decided that the default copy operations were correct and then (correctly) decided not to mention them because the default copy operations are superior to user-defined ones (e.g. because of ABI issues). In C++0x, a programmer can be explicit by defaulting copy, but that could be considered undesirably verbose.
So by writing Vec
as follows:
class Vec {
vector<int> v;
int my_int; // the index of my favorite int stored in v
public:
Vec() = default;
Vec(Vec const&) = default;
Vec& operator=(Vec const&) = default;
~Vec() = default;
};
we can state that the default copy operations are safe, while the default move operations are not safe. If the user had to explicitly define the move operations as deleted, this would "leave[..] a hole in the expressions" (see N3053: trying to copy from, or return (non-const) rvalues will attempt to use the deleted move operations.
It is not entirely obvious why the dtor should be included in that list, but I think it belongs to the group of 3/5 special member functions where class invariants often occur.
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