0

I was trying to see why move constructor is called, as below:

#include<iostream>
#include<utility>
using namespace std;
struct M{
  M(){cout<<"ctor\n";}
  M(const M&m){cout<<"copy ctor\n";}
  M(M&&m){cout<<"move ctor\n";}
  M& operator=(const M&m){cout<<"copy operator=\n"; return *this;}
  M& operator=(M&&m){cout<<"move operator=\n"; return *this;}
};
int main(){
  M obj1(M{}); // why this prints "ctor", but not "move ctor"?
  cout << "----\n";
  M obj2(move(M{})); // ctor and move ctor, as expected.
  return 0;
}

compile with clang++-14 and msvc-2022, both gave same result:

ctor
----
ctor
move ctor

My question lies in M obj1(M{}): as long as M{} is a temporary object, it's right value, so I expected that move ctor will be called. But in fact not.

Only when I explicitly called M obj2(move(M{}));, this time, ctor + move ctor being called.

So M{} is an r-value, move(M{}) returns an r-value, why they gave different result on object construction?

Thanks!

Troskyvs
  • 7,537
  • 7
  • 47
  • 115
  • Because there is non-mandatory copy elision in C++14, which is used in `M obj1(M{})`. Use compiler flag `-fno-elide-constructors` to see that this is the case. [Demo](https://wandbox.org/permlink/v5Gm10gKO07PFUiS) – Jason Sep 12 '22 at 03:21

1 Answers1

3

My question lies in M obj1(M{}): as long as M{} is a temporary object, it's right value, so I expected that move ctor will be called. But in fact not.

This is because in C++14 there is non-mandatory copy elision. This means that under some circumstances compilers are allowed to directly create object obj1 using the default ctor(in your example).

This can be seen from copy elison:

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. The objects are constructed directly into the storage where they would otherwise be copied/moved to. This is an optimization: even when it takes place and the copy/move (since C++11) constructor is not called, it still must be present and accessible (as if no optimization happened at all), otherwise the program is ill-formed:

  • In the initialization of an object, when the source object is a nameless temporary and is of the same class type (ignoring cv-qualification) as the target object.

(emphasis mine)

And since in your example the source object M{} is a nameless temporaray and is of the same class type M as the target object, the above quoted statement applies and so copy/move elision happens. This means that obj1 is constructed using the default constructor.


You can even confirm that this is the case by providing flag -fno-elide-constructors to the compiler. Demo. In this demo, you'll notice that now you get the move ctor call in the output as expected.

Output of program with -fno-elide-constructors flag

ctor
move ctor    <-----------------------this is called with -fno-elide-constructors flag
----
ctor
move ctor
Jason
  • 36,170
  • 5
  • 26
  • 60