Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c++

c++11

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.

like image 872
AlwaysLearning Avatar asked Jul 27 '15 14:07

AlwaysLearning


People also ask

Is there a default destructor in C++?

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.

Are move constructor automatically generated?

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.

What does a default move constructor do?

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: &&.

Does compiler provide default move constructor?

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.


1 Answers

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:

  1. 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])
  2. 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.

like image 53
dyp Avatar answered Oct 05 '22 03:10

dyp