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
t2is constructed directly from theprvaluethatget_testreturns. And since aprvalueis used to constructt2, the move constructor is used. Note that in C++17, the flag-fno-elide-constructorshave 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-constructorsflag, a temporaryprvalueis returned byget_testusing the move constructor. So you see the first call to the move ctor. Then, that temporary is used to initializet2again using the move constructor and hence we get the second call to the move ctor.