Reputation: 63
I am trying to get the grasp of rvalue references and move semantics with a simple self-made example but I can't understand a specific part. I have created the following class:
class A {
public:
A(int a) {
cout << "Def constructor" << endl;
}
A(const A& var) {
cout << "Copy constructor" << endl;
}
A(A&& var) {
cout << "Move constructor" << endl;
}
A& operator=(const A& var) {
cout << "Copy Assignment" << endl;
return *this;
}
A& operator=(A&& var) {
cout << "Move Assignment" << endl;
return *this;
}
};
I tried the following experiments to see if I can predict how the constructors/operators are going to be called:
Now, I created a simple function that just returns an A object.
A helper() {
return A(1);
}
Now comes the part I don't understand. I created another function that is completely pointless. It takes an A object by value and it just returns it.
A helper_alt(A a) {
return a;
}
Please, if any of what I said is wrong or you feel I might have not understood something, feel free to correct me.
My actual question: In the last case, why is the move constructor being called and then the move assignment operator, instead of just the move assignment operator?
Upvotes: 6
Views: 1413
Reputation: 171
consider the below example. I have compiled the sample code using -fno-elide-constructors flag to prevent RVO optimizations:
g++ -fno-elide-constructors -o test test.cpp
#include<iostream>
using namespace std;
class A {
public:
A(int a) {
cout << "Def constructor" << endl;
}
A(const A& var) {
cout << "Copy constructor" << endl;
}
A(A&& var) {
cout << "Move constructor" << endl;
}
A& operator=(const A& var) {
cout << "Copy Assignment" << endl;
return *this;
}
A& operator=(A&& var) {
cout << "Move Assignment" << endl;
return *this;
}
};
A a_global(1);
A helper_alt(A a) {
return a;
}
A helper_a_local(A a) {
A x(1);
return x;
}
A helper_a_global(A a) {
return a_global;
}
int main(){
A a1(1);
A a4(4);
std::cout << "================= helper_alt(a1) ==================" << std::endl;
a4 = helper_alt(a1);
std::cout << "=============== helper_a_local() ================" << std::endl;
a4 = helper_a_local(a1);
std::cout << "=============== helper_a_global() ================" << std::endl;
a4 = helper_a_global(a1);
return 0;
}
This will result in the below output:
Def constructor
Def constructor
Def constructor
================= helper_alt(a1) ==================
Copy constructor
Move constructor
Move Assignment
=============== helper_a_local() ================
Copy constructor
Def constructor
Move constructor
Move Assignment
=============== helper_a_global() ================
Copy constructor
Copy constructor
Move Assignment
In simple words, C++ constructs a new temporary object (rvalue
) when the return type is not a reference, which results in calling Move or Copy constructor depending on the value category and the lifetime of the returned object.
Anyway, I think the logic behind calling the constructor is that you are not working with reference, and returned identity should be construed first, either by copy or move constructor, depending on the returned value category
or lifetime of the return object. As another example:
A helper_move_vs_copy(A a) {
// Call the Copy Constructor
A b = a;
// Call the Move Constructor, Due to the end of 'a' lifetime
return a;
}
int main(){
A a1(1);
A a2(4);
std::cout << "=============== helper_move_vs_copy() ================" << std::endl;
helper_move_vs_copy(a1);
return 0;
}
which outputs:
Def constructor
Def constructor
=============== helper_move_vs_copy() ================
Copy constructor
Copy constructor
Move constructor
From cppreference:
an xvalue (an “eXpiring” value) is a glvalue that denotes an object whose resources can be reused;
At last, it is the job of RVO
to decrease unnecessary moves and copies by optimization of the code, which can even result in an optimized binary for basic programmers!
Upvotes: 2
Reputation: 25593
Congratulations, you found a core issue of C++!
There are still a lot of discussions around the behavior you see with your example code.
There are suggestions like:
A&& helper_alt(A a) {
std::cout << ".." << std::endl;
return std::move(a);
}
This will do what you want, simply use the move assignment but emits a warning from g++ "warning: reference to local variable 'a' returned", even if the variable goes immediately out of scope.
Already other people found that problem and this is already made a c++ standard language core issue
Interestingly the issue was already found in 2010 but not solved until now...
To give you an answer to your question "In the last case, why is the move constructor being called and then the move assignment operator, instead of just the move assignment operator?" is, that also C++ committee does not have an answer until now. To be precise, there is a proposed solution and this one is accepted but until now not part of the language.
From: Comment Status
Amend paragraph 34 to explicitly exclude function parameters from copy elision. Amend paragraph 35 to include function parameters as eligible for move-construction.
Upvotes: 3