Why not synthesize move operations for a class that defines a destructor as = default?

The rule that the compiler should not synthesize move operations for a class declaring a destructor or copy operation (i.e. copy constructor / assignment) makes sense. In the end, by declaring these operations, the class recognizes that it needs to do some kind of user bookkeeping.

However, this reasoning does not apply if the class defines a destructor or copy operation as =default ? Should this case not be an exception to the rule then?

EDIT: One of the reasons I can define a destructor as =default , but not other special operations, is when I need a virtual destructor for the base class, so I have to define it to make it virtual.

+6
source share
2 answers

N3174 seems to have been a proposal that introduced a rule declared by the user dtor / copy op => no implicit move. In this article, Bjarne Straustrup 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 relationship between two objects (here, members) that are not specified anywhere declaratively in the code.

Please note that this invariant is broken by move operations, which is especially malicious if they are implicitly generated and change the value of code that was previously well executed (in C ++ 03, where rvalues ​​are copied).

Because of these invariants, Straustrup suggested:

  • Moving and copying are generated by default (if only if their elements are moved or copied, as indicated in the FCD [dyp: FCD = draft final committee])
  • If any move, copy, or destructor is explicitly specified (declared, defined, =default or =delete ) by the user, the copy or move will not be generated by default.

(Later, he suggests only abandoning the implicit generation of a copy in 2., and not completely remove it in C ++ 11)

The best explanation I can find in N3174 why the default user-declared operations are included in the second marker point is the relationship between invariants and special member functions:

I think the most disturbing cases are equivalent to Vec . There is an implicit invariant for such classes, but no “hints” to help the compiler [dyp: detecting the existence of an invariant] in the form of a custom copy constructor. This kind of example, for example, occurs 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 (for example, due to problems with ABI). In C ++ 0x, a programmer may be explicit by default, but this might be considered undesirable 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; }; 

You can specify that copy operations are safe by default, and move operations by default are unsafe. If the user had to explicitly define the move operations as deleted, this will “leave a [..] hole in the expressions” (see N3053 : trying to copy or return (not const) rvalues ​​will try to use the remote move operations.

It is not clear why dtor should be included in this list, but I think that it belongs to a group of 3/5 special member functions, where class invariants often arise.

+3
source

I think part of the reason is that if you wrote a default by default, then you are already writing (at least) C ++ 11, and therefore there is no reason why you shouldn't also write a default move constructor and move destination operator.

This rule protects against classes written by pre-C ++ 11 from the dumb defined semantics of movement that might violate class invariants. If the class is not pre-C ++ 11, you can explicitly indicate whether you want to move the semantics and enable them with =default .

+2
source

All Articles