I've read several posts saying the function signature for allowing move semantics when returning a local variable should look exactly like it is returning by value and as long as the move constructor/move assignment operator are defined then either RVO or move semantics will take place depending on the situation.
Although I've read many say that your return type should almost never be an rvalue reference since it returns a dangling reference in the case of a local moved variable or can prevent RVO, I'm wondering if it is correct when the object being moved has a lifetime that extends beyond the return of the function, such as moving a global object or a member object of an object that will continue to live on.
Here is a small test program to illustrate my question:
#include <iostream>
using namespace std;
class Test
{
public:
Test() = delete;
Test(int _x)
{
cout << "Test(int) x: " << _x << endl;
if(nullptr != x)
delete x;
x = new int(_x);
}
~Test()
{
cout << "~Test() x: ";
if(nullptr != x)
{
cout << *x;
delete x;
}
else
cout << "nullptr";
cout << endl;
}
Test(const Test &rhs)
{
cout << "Test(const Test&) rhs x: " << *(rhs.x) << endl;
if(nullptr != x)
delete x;
x = new int(*(rhs.x));
}
Test(Test &&rhs)
{
cout << "Test(Test&&) rhs x: " << *(rhs.x) << endl;
if(nullptr != x)
delete x;
x = rhs.x;
rhs.x = nullptr;
}
Test& operator=(const Test &rhs)
{
cout << "operator=(const Test&) rhs x: " << *(rhs.x) << endl;
if(nullptr != x)
delete x;
x = new int(*(rhs.x));
return *this;
}
Test& operator=(Test &&rhs)
{
cout << "operator=(Test&&) rhs x: " << *(rhs.x) << endl;
if(nullptr != x)
delete x;
x = rhs.x;
rhs.x = nullptr;
return *this;
}
private:
int *x = nullptr;
};
Test global_test(7);
Test&& get_test()
{
return std::move(global_test);
}
int main()
{
Test local_test(5);
local_test = get_test();
//auto local_test = get_test();
return 0;
}
With get_test() explicitly returning an rvalue reference the output of the above is:
Test(int) x: 7
Test(int) x: 5
operator=(Test&&) rhs x: 7
~Test() x: 7
~Test() x: nullptr
which is exactly what I want, only the move assignment operator being called when an existing Test steals another, non-local Test's data. But if the function is changed to:
Test get_test()
then the output changes to:
Test(int) x: 7
Test(int) x: 5
Test(Test&&) rhs x: 7
operator=(Test&&) rhs x: 7
~Test() x: nullptr
~Test() x: 7
~Test() x: nullptr
which is ok since no copy is taking place but it is still slower than the original as it looks like the compiler creates a temporary Test object from the moved global_test inside assign_test() and then move assigns it into local_test.
If I change which lines are commented in main to this:
int main()
{
//Test local_test(5);
//local_test = get_test();
auto local_test = get_test();
return 0;
}
and have get_test() explicitly return an rvalue reference as in the original example, the output shows:
Test(int) x: 7
Test(Test&&) rhs x: 7
~Test() x: 7
~Test() x: nullptr
which is what we want but changing the signature again to:
Test get_test()
does not change the output at all. It could be that when local_test is being constructed using get_test(), get_test() is essentially being inlined. Is it ok to always explicitly return an rvalue reference in this situation, when the object being moved is not local? Is the only time explicitly returning an rvalue reference can prevent RVO when the object being moved is local?
Aucun commentaire:
Enregistrer un commentaire