Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is the move constructor involved here

I have this piece of C++ code:

class Args {};

class MyClass {
  public:
  MyClass(Args& a) {}
  MyClass(MyClass &&) = delete;
};

int main() {

  Args a;
  MyClass c1 = MyClass(a);
  MyClass c2 = a;
  MyClass c3(a);

  return 0;
}

This does not compile because the construction of objects c1 and c2 seem to involve the class's move constructor:

error: use of deleted function ‘MyClass::MyClass(MyClass&&)’

It seems as if the compiler wants to create temporary object and then move them to c1 and c2. Why is this happening? Shouldn't all three statements just call the MyClass(Args& a) constructor?

On the other hand, if I do create the move constructor the program compiles fine and the move constructor is never called!!!

like image 737
skalkoto Avatar asked Jun 27 '18 10:06

skalkoto


3 Answers

See copy elision:

Under the following circumstances, the compilers are permitted, but not required to omit the copy- and move- (since C++11) construction of class objects even if the copy/move (since C++11) constructor and the destructor have observable side-effects. This is an optimization: even when it takes place and the copy-/move-constructor is not called, it still must be present and accessible (as if no optimization happened at all), otherwise the program is ill-formed.

Since C++17:

They need not be present or accessible, as the language rules ensure that no copy/move operation takes place, even conceptually.

like image 127
Maxim Egorushkin Avatar answered Nov 16 '22 02:11

Maxim Egorushkin


One major problem is this:

MyClass c1 = MyClass(a);

This creates a temporary object of type MyClass, and temporary types are rvalues. It then tries to either copy-construct c1 using the temporary object, or if you have a move-constructor then move-construct c1.

In C++11 and C++14, there will be no auto-generated copy-constructor for your class (because you have a user-defined (even if deleted) move-constructor), so only the deleted move-constructor is available. Well it would be available if it wasn't deleted, leading to your error.

like image 31
Some programmer dude Avatar answered Nov 16 '22 02:11

Some programmer dude


Why is this happening? Shouldn't all three statements just call the MyClass(Args& a) constructor?

For both MyClass c1 = MyClass(a); and MyClass c2 = a;, temporary MyClass will be constructed at first by the constructor MyClass::MyClass(Args& a), then used to copy-initialize the object c1 and c2. The constructed temporaries are rvalues, that means the move-constructor will be selected for the copy initialization.

On the other hand, if I do create the move constructor the program compiles fine and the move constructor is never called!!!

The reason is copy elision; the copy/move operation is omitted here, results in the fact that MyClass::MyClass(Args& a) is used to initialize the object c1 and c2 directly.

The rule about copy elision changed since C++17. Note that pre-C++17, copy elision is not guaranteed. And for non-guaranteed copy elision, even when the copy/move operation is omitted the copy/move constructor still needs to be present and accessible.

This is an optimization: even when it takes place and the copy-/move-constructor is not called, it still must be present and accessible (as if no optimization happened at all), otherwise the program is ill-formed.

After C++17 your code would work fine. For guaranteed copy elision, copy/move constructor is not required to be present or accessible.

Under the following circumstances, the compilers are required to omit the copy- and move- construction of class objects even if the copy/move constructor and the destructor have observable side-effects. They need not be present or accessible, as the language rules ensure that no copy/move operation takes place, even conceptually:

  • In initialization, if the initializer expression is a prvalue and the cv-unqualified version of the source type is the same class as the class of the destination, the initializer expression is used to initialize the destination object:

    T x = T(T(T())); // only one call to default constructor of T, to initialize x
    
like image 35
songyuanyao Avatar answered Nov 16 '22 00:11

songyuanyao