How to return an element of a local object
Consider the following code:
struct Foo
{
Foo() { cout << "Foo()\n"; }
~Foo() { cout << "~Foo()\n"; }
Foo(Foo&) { cout << "Foo(Foo&)\n"; }
Foo(Foo&&) { cout << "Foo(Foo&&)\n"; }
int d;
};
struct Bar
{
Foo bigData;
void workOnBigData() { /*...*/ }
}
Foo getBigData()
{
Bar b;
b.workOnBigData();
return b.bigData;
}
What is the best way to implement getBigData()
in terms of ellision / move copy semantics? In this implementation, the compiler doesn't seem to be allowed to move bigData
. I've tested the following features:
Foo f()
{
Foo foo;
return foo; // RVO
}
Foo g()
{
Bar b;
return b.bigData; // Copy
}
Foo h()
{
Bar b;
auto r = move(b.bigData);
return r; // Move
}
Can you explain the results of these implementations and show the most efficient way to return a member of a local object.
source to share
There are many ways to avoid extra copies, imho would be closer to your code:
Foo getBigData()
{
Foo ret; // do a cheap initialization
Bar b;
b.workOnBigData();
std::swap(ret, b.bigData); // 'steal' the member here
return ret; // NRVO can apply
}
The same can be achieved by moving the construct of the returned object
Foo getBigData()
{
Bar b;
b.workOnBigData();
Foo ret(std::move(b.bigData)); // these two lines are equivalent to
return ret; // return std::move(b.bigData);
}
source to share
I think the answers to this question are Why didn't the copy constructor move here? really helpful for answering your question. Copying elision is not used in your example
Foo getBigData()
{
Bar b;
b.workOnBigData();
return b.bigData;
}
since this request is blank ( http://en.cppreference.com/w/cpp/language/copy_elision ):
return statement expression is the name of a non-volatile object with automatic storage time ... and which is of the same type (ignoring the top-level cv qualification) as the return type of the function, then copy / move is omitted
In your example, Bar is an auto-storage variable, but your return is Foo. If you change the Bar class, the compiler starts using the copy:
#include <iostream>
#include <typeinfo>
using namespace std;
struct Foo
{
Foo() { cout << "Foo()\n"; }
~Foo() { cout << "~Foo()\n"; }
Foo(const Foo&) { cout << "Foo(Foo&)\n"; }
Foo(Foo&&) { cout << "Foo(Foo&&)\n"; }
int d;
};
struct Bar
{
Foo bigData;
void workOnBigData() { /*...*/ }
};
struct Bar2
{
void workOnBigData(Foo&) { /*...*/ }
};
Foo getBigData()
{
Bar b;
b.workOnBigData();
return b.bigData;
}
Foo getBigData2()
{
Foo f;
Bar2 b;
b.workOnBigData(f);
return f;
}
int main()
{
{
Foo f = getBigData();
}
cout << "---" << endl;
{
Foo f = getBigData2();
}
}
#include <iostream>
#include <typeinfo>
using namespace std;
struct Foo
{
Foo() { cout << "Foo()\n"; }
~Foo() { cout << "~Foo()\n"; }
Foo(const Foo&) { cout << "Foo(Foo&)\n"; }
Foo(Foo&&) { cout << "Foo(Foo&&)\n"; }
int d;
};
struct Bar
{
Foo bigData;
void workOnBigData() { /*...*/ }
};
struct Bar2
{
void workOnBigData(Foo&) { /*...*/ }
};
Foo getBigData()
{
Bar b;
b.workOnBigData();
return b.bigData;
}
Foo getBigData2()
{
Foo f;
Bar2 b;
b.workOnBigData(f);
return f;
}
int main()
{
{
Foo f = getBigData();
}
cout << "---" << endl;
{
Foo f = getBigData2();
}
}
This is what it outputs:
$ ./a.out
Foo()
Foo(Foo&)
~Foo()
~Foo()
---
Foo()
~Foo()
source to share