I am learning about move semantics, so I wrote a small program as follows to practice:
#include <iostream>
using namespace std;
int one_int = 123;
class A {
public:
int *a;
A(int *ptr) : a(ptr) {
cout << "In A's constructor..." << endl;
}
A(const A &other) {
cout << "In A's copy constructor..." << endl;
a = other.a;
}
A(A &&other) noexcept {
cout << "In A's move constructor..." << endl;
a = other.a;
other.a = nullptr;
}
~A() {
cout << "In A's destructor..." << endl;
}
};
A make_obj() {
cout << "In make_obj..." << endl;
return A(&one_int);
}
A make_obj_by_move(A &&source) {
cout << "In make_obj_by_move..." << endl;
return A(static_cast<A&&>(source));
}
A make_obj_by_copy(A source) {
cout << "In make_obj_by_copy..." << endl;
return A(source);
}
int main() {
A obj2 = make_obj_by_move(make_obj());
cout << endl;
A obj1 = make_obj_by_copy(make_obj());
cout << endl;
// to ensure that the information printed when obj1 and obj2 are destroyed is separated from the above ones
obj1.a = nullptr;
obj2.a = nullptr;
return 0;
}
I turned off RVO/NRVO of clang by the -fno-elide-constructors compiler option and use -std=c++14. I got the following output, very confusing:
In make_obj...
In A's constructor... // make tmp obj
In A's move constructor... // w/o RVO, need another construction to get return value?
In A's destructor... // destory the temp obj in make_obj scope?
In make_obj_by_move...
In A's move constructor... // call the move constructor like expected? I guess by the position of "copy constructor" below
In A's move constructor... // what happened here ???
In A's destructor... // destory which?
In A's move constructor... // w/o NRVO, need another construction to get obj1?
In A's destructor... // destory which?
In A's destructor... // destory which?
In make_obj...
In A's constructor... // make tmp obj
In A's move constructor... // w/o RVO, need another construction to get return value?
In A's destructor... // destory the temp obj in make_obj scope?
In A's move constructor... // what happened here ???
In make_obj_by_copy...
In A's copy constructor... // call the copy constructor like expected
In A's move constructor... // what happened here ???
In A's destructor... // destory which?
In A's move constructor... // w/o NRVO, need another construction to get obj2?
In A's destructor... // destory which?
In A's destructor... // destory which?
In A's destructor... // destory which?
In A's destructor... // destory obj1/obj2
In A's destructor... // destory obj2/obj1
What exactly happened here? Why have many unexpected move constructions and destructions been called? I tried to explain some of the output, but there are still many lines of output that I don't understand. Can you help me? Thank you very much.
In the absence of any copy elision, there are usually three objects involved in returning from a function:
returnkeywordThe object you give to the
returnkeyword is (often) a function-local object and storage for it lives within the function's stack frame.The function's return value exists outside the function's scope, within the caller's stack frame as a temporary object that exists only until the end of the full-expression in which the function was called.
The object that gets initialized by the return value also exists in the caller's stack frame and (usually) has a name and some wider scope within the caller
Copy elision lets the compiler collapse all three of those objects into one in the correct circumstances.
I've annotated your program's output to show exactly which objects each line is talking about:
As a side-note, in these sorts of situations it can be useful to print the value of the
thispointer in each of your instrumented operations so that you can easily see exactly which object the operation was performed on.