struct Test {
int field = 30;
Test() { cout << "In ctor" << endl; }
Test(const Test &other) { field = other.field; cout << "In copy ctor" << endl; }
Test(Test &&other) { field = other.field; cout << "In move ctor" << endl; }
Test &operator=(const Test &other) { field = other.field; cout << "In copy assignment" << endl; return *this; }
Test &operator=(Test &&other) { field = other.field; cout << "In move assignment" << endl; return *this; }
~Test() { cout << "In dtor" << endl; }
};
Test get_test() {
Test t;
return t;
}
int main() {
Test t2 = get_test();
}
I think this is the canonical NRVO example. I'm compiling with -fno-elide-constructors
and I see that the following are called: ctor, move ctor, dtor, dtor.
So the first ctor call corresponds to the line Test t;
, the move ctor is constructing the t2
in main
, then the temporary that is returned from get_test
is destroyed, then the t2
in main
is destroyed.
What I don't understand is: shouldn't there be a copy ctor invocation when returning by value? That is, I thought that get_test
should be making a copy of t
and then this copy is moved into t2
. It seems like t
is moved into t2
right away.
C++17
Starting from C++17, there is mandatory copy elison which says:
(emphasis mine)
This means that
t2
is constructed directly from theprvalue
thatget_test
returns. And since aprvalue
is used to constructt2
, the move constructor is used. Note that in C++17, the flag-fno-elide-constructors
have no effect on return value optimization(RVO) and is different from NRVO.Pre-C++17
But prior to C++17, there was non-mandatory copy elison and since you've provided the
-fno-elide-constructors
flag, a temporaryprvalue
is returned byget_test
using the move constructor. So you see the first call to the move ctor. Then, that temporary is used to initializet2
again using the move constructor and hence we get the second call to the move ctor.