Reputation: 8041
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.
Upvotes: 7
Views: 526
Reputation: 39141
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.
Upvotes: 4
Reputation: 171383
I think part of the reason is that if you've written a defaulted destructor then you are already writing (at least) C++11 and so there is no reason you should not be able to also write a defaulted move constructor and move assignment operator.
The rule protects against classes written pre-C++11 from silently being given move semantics that might break the class invariants. If the class is not pre-C++11 then you can decide explicitly whether you want move semantics and enable them with =default
.
Upvotes: 2