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:
return
keywordThe object you give to the return
keyword 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:
In make_obj...
In A's constructor... // create function-local temporary object
In A's move constructor... // construct make_obj's return value by move from the function local temp object
In A's destructor... // destroy the function-local temp object
In make_obj_by_move...
In A's move constructor... // create function-local temporary object
In A's move constructor... // construct make_obj_by_move's return value by move from the function-local temp object
In A's destructor... // destroy the function-local temp object
In A's move constructor... // construct obj1 in main by move from make_obj_by_move's return object
In A's destructor... // destroy make_obj_by_move's return object
In A's destructor... // destroy make_obj's return object
In make_obj...
In A's constructor... // create function-local temporary object
In A's move constructor... // construct make_obj's return value by move from the function local temp object
In A's destructor... // destroy the function-local temp object
In A's move constructor... // construct make_obj_by_copy's source parameter by move from make_obj's return object
In make_obj_by_copy...
In A's copy constructor... // create function-local temp object by copy from the source parameter
In A's move constructor... // construct make_obj_by_copy's return value by move from the function-local temp object
In A's destructor... // destroy the function-local temp object
In A's move constructor... // construct obj2 in main by move from make_obj_by_move's return object
In A's destructor... // destroy make_obj_by_copy's return object
In A's destructor... // destroy make_obj_by_copy's source parameter
In A's destructor... // destroy make_obj's return object
In A's destructor... // destroy obj2
In A's destructor... // destroy obj1
As a side-note, in these sorts of situations it can be useful to print the value of the this
pointer in each of your instrumented operations so that you can easily see exactly which object the operation was performed on.