In the next test code, we have a simple class MyClass
with only one variable member (int myValue
) and one function (MyClass getChild()
) that returns a new instance of MyClass
. This class has the main operators overloaded to print when they are called.
We have three functions with two parameters that perform a simple assignment (first_param = second_param): :
func1
: the second parameter is an rvalue reference (also uses astd::forward
)
void func1(MyClass &el, MyClass &&c) {
el = std::forward<MyClass>(c);
}
func2
: the second parameter is a const lvalue reference
void func2(MyClass &el, const MyClass &c) {
el = c;
}
func3
: two overloads (one equivalent tofunc1
and another equivalent tofunc2
)
void func3(MyClass &el, MyClass &&c) {
el = std::forward<MyClass>(c);
}
void func3(MyClass &el, const MyClass &c) {
el = c;
}
In the main()
function we call these three functions three times each, one passing an rvalue, another passing an lvalue, and another passing std::move(lvalue)
(or what is the same, an rvalue reference). Prior to calling these functions, we also do a direct assignment (without calling any function) for an lvalue, rvalue, and rvalue reference.
The test code:
#include <iostream>
#include <utility>
class MyClass {
public:
int myValue;
MyClass(int n) { // custom constructor
std::cout << "MyClass(int n) [custom constructor]" << std::endl;
}
MyClass() { // default constructor
std::cout << "MyClass() [default constructor]" << std::endl;
}
~MyClass() { // destructor
std::cout << "~MyClass() [destructor]" << std::endl;
}
MyClass(const MyClass& other) // copy constructor
: myValue(other.myValue)
{
std::cout << "MyClass(const MyClass& other) [copy constructor]" << std::endl;
}
MyClass(MyClass&& other) noexcept // move constructor
: myValue(other.myValue)
{
std::cout << "MyClass(MyClass&& other) [move constructor]" << std::endl;
}
MyClass& operator=(const MyClass& other) { // copy assignment
myValue = other.myValue;
std::cout << "MyClass& operator=(const MyClass& other) [copy assignment]" << std::endl;
return *this;
}
MyClass& operator=(MyClass&& other) noexcept { // move assignment
myValue = other.myValue;
std::cout << "MyClass& operator=(MyClass&& other) [move assignment]" << std::endl;
return *this;
}
MyClass getChild() const {
return MyClass(myValue+1);
}
};
void func1(MyClass &el, MyClass &&c) {
el = std::forward<MyClass>(c);
}
void func2(MyClass &el, const MyClass &c) {
el = c;
}
void func3(MyClass &el, MyClass &&c) {
el = std::forward<MyClass>(c);
}
void func3(MyClass &el, const MyClass &c) {
el = c;
}
int main(int argc, char** argv) {
MyClass root(200);
MyClass ch = root.getChild();
MyClass result;
std::cout << "==================================================================" << std::endl;
std::cout << "------------- simple assignment to rvalue ------------------------" << std::endl;
result = root.getChild();
std::cout << "------------- simple assignment to lvalue ------------------------" << std::endl;
result = ch;
std::cout << "------------- simple assignment to std::move(lvalue) -------------" << std::endl;
result = std::move(ch);
std::cout << "==================================================================" << std::endl;
std::cout << "------------- func1 with rvalue ----------------------------------" << std::endl;
func1(result, root.getChild());
std::cout << "------------- func1 with lvalue ----------------------------------" << std::endl;
//func1(result, ch); // does not compile
std::cout << "** Compiler error **" << std::endl;
std::cout << "------------- func1 with std::move(lvalue) -----------------------" << std::endl;
func1(result, std::move(ch));
std::cout << "==================================================================" << std::endl;
std::cout << "------------- func2 with rvalue ----------------------------------" << std::endl;
func2(result, root.getChild());
std::cout << "------------- func2 with lvalue ----------------------------------" << std::endl;
func2(result, ch);
std::cout << "------------- func2 with std::move(lvalue) -----------------------" << std::endl;
func2(result, std::move(ch));
std::cout << "==================================================================" << std::endl;
std::cout << "------------- func3 with rvalue ----------------------------------" << std::endl;
func3(result, root.getChild());
std::cout << "------------- func3 with lvalue ----------------------------------" << std::endl;
func3(result, ch);
std::cout << "------------- func3 with std::move(lvalue) -----------------------" << std::endl;
func3(result, std::move(ch));
std::cout << "==================================================================" << std::endl;
return 0;
}
After compiling it with g++ (it doesn't matter if with -O0 or -O3) and running it the result is:
MyClass(int n) [custom constructor]
MyClass(int n) [custom constructor]
MyClass() [default constructor]
==================================================================
------------- simple assignment to rvalue ------------------------
MyClass(int n) [custom constructor]
MyClass& operator=(MyClass&& other) [move assignment]
~MyClass() [destructor]
------------- simple assignment to lvalue ------------------------
MyClass& operator=(const MyClass& other) [copy assignment]
------------- simple assignment to std::move(lvalue) -------------
MyClass& operator=(MyClass&& other) [move assignment]
==================================================================
------------- func1 with rvalue ----------------------------------
MyClass(int n) [custom constructor]
MyClass& operator=(MyClass&& other) [move assignment]
~MyClass() [destructor]
------------- func1 with lvalue ----------------------------------
** Compiler error **
------------- func1 with std::move(lvalue) -----------------------
MyClass& operator=(MyClass&& other) [move assignment]
==================================================================
------------- func2 with rvalue ----------------------------------
MyClass(int n) [custom constructor]
MyClass& operator=(const MyClass& other) [copy assignment]
~MyClass() [destructor]
------------- func2 with lvalue ----------------------------------
MyClass& operator=(const MyClass& other) [copy assignment]
------------- func2 with std::move(lvalue) -----------------------
MyClass& operator=(const MyClass& other) [copy assignment]
==================================================================
------------- func3 with rvalue ----------------------------------
MyClass(int n) [custom constructor]
MyClass& operator=(MyClass&& other) [move assignment]
~MyClass() [destructor]
------------- func3 with lvalue ----------------------------------
MyClass& operator=(const MyClass& other) [copy assignment]
------------- func3 with std::move(lvalue) -----------------------
MyClass& operator=(MyClass&& other) [move assignment]
==================================================================
~MyClass() [destructor]
~MyClass() [destructor]
~MyClass() [destructor]
For the assignment, the result is as expected. If you pass an rvalue, it calls to move assignment, if you pass an lvalue, it calls to copy assignment, and if you pass an rvalue reference (std::move(lvalue)
) it calls to a move assignment.
The calls to func1
are also the expected (remember that this function receives an rvalue reference). If you pass an rvalue, it calls to move assignment, if you pass an lvalue, the compilation fails (because an lvalue can't bind to rvalue reference), and if you pass an rvalue reference (std::move(lvalue)
) it calls to a move assignment.
But for func2
, in the three cases, the copy assignment is called. This function receives as a second parameter a const lvalue reference, this is an lvalue and then it calls to copy assignment. I understand this, but, why the compiler does not optimize this function when it is called with a temporal object (rvalue or rvalue reference) calling the move assignment operator instead of the copy assignment?
The func3
is an attempt to create a function that works in the same way as a direct assignment, combining the func1
behaviour and defining an overload with func2
behaviour for when an lvalue is passed. This works, but this solution requires the function code to be duplicated into the two functions (not exactly, since in one solution we have to use std::forward
). Is there a way to achieve this by avoiding having to duplicate the code? This function is small but could be larger in other contexts.
In summary, there are two questions:
Why is the func2
function not optimized to call the move assignment when it receives an rvalue or an rvalue reference?
How could I modify the func3
function so as not to have to "duplicate" the code?
Aucun commentaire:
Enregistrer un commentaire