Roman Roman - 3 years ago 103
C++ Question

Will std::move() upon object construction in return statement help or prevent RVO?

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]

Answer Source
// 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.

Answer

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.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download