Due to widely ranging responses from the community, I am asking this in hopes to debunk implementation-specific responses from stack-overflow users.
Which of these is best-practice (offers greatest optimization)?
// version 1
MyObject Widget::GetSomething() {
return MyObject();
}
// version 2
MyObject Widget::GetSomething() {
return std::move(MyObject());
}
// version 3
MyObject Widget::GetSomething() {
auto obj = MyObject()
return obj;
}
// version 4
MyObject Widget::GetSomething() {
auto obj = MyObject()
return std::move(obj);
}
EDIT: Thank you to Yakk, for the direct, respectful answer. [accepted answer]
// version 1
MyObject Widget::GetSomething() {
return MyObject();
}
In C++03 this requires MyObject
by copyable. At runtime, no copy will be made using any "real" compiler with reasonable settings as the standard permits elision here.
In C++11 or 14 it requires the object be movable or copyable. Elision remains; no move or copy is done.
In C++17 there is no move or copy here to elide.
In every case, in practice, MyObject
is directly constructed in the return value.
// version 2
MyObject Widget::GetSomething() {
return std::move(MyObject());
}
This is invalid in C++03.
In C++11 and beyond, MyObject
is moved into the return value. The move must occur at runtime (barring as-if elimination).
// version 3
MyObject Widget::GetSomething() {
auto obj = MyObject();
return obj;
}
Identical to version 1, except C++17 behaves like C++11/14. In addition, the elision here is more fragile; seemingly innocuous changes could force the compiler to actually move obj
.
Theoretically 2 moves are elided here in C++11/14/17 (and 2 copies in C++03). The first elision is safe, the second fragile.
// version 4
MyObject Widget::GetSomething() {
auto obj = MyObject();
return std::move(obj);
}
In practice this behaves just like version 2. An extra move (copy in C++03) occurs in constructing obj
but it is elided, so nothing happens at runtime.
Elision permits the elimination of side effects of the copy/move; the objects lifetimes are merged into one object, and the move/copy is eliminated. The constructor still has to exist, it is just never called.
Both 1 and 3 will compile to identical runtime code. 3 is slightly more fragile.
Both 2 and 4 compile to identical runtime code. It should never be faster than 1/3, but if the move can be eliminated by the compiler proving not doing it is the same as-if doing it, it could compile to the same runtime code as 1/3. This is far from guaranteed, and extremely fragile.
So 1>=3>=2>=4
is the order of faster to slower in practice, where "more fragile" code that is otherwise the same speed is <=
.
As an example of a case that could make 3 slower than 1, if you had an if statement:
// version 3 - modified
MyObject Widget::GetSomething() {
auto obj = MyObject();
if (err()) return MyObject("err");
return obj;
}
suddenly many compilers will be forced to move obj
into the return value instead of eliding obj
and the return value together.