40 comments

[ 3.4 ms ] story [ 83.2 ms ] thread
clang 3.7 has a warning when std::move prevents it from using return value optimization:

    foo.cpp:44:25: error: moving a temporary object prevents copy elision [-Werror,-Wpessimizing-move]
        auto result = std::move(worker.run());

    foo.cpp:44:25: note: remove std::move call here
        auto result = std::move(worker.run());
That's a welcome feature indeed. (Too bad the 3.7 release is still a month away.)
Sounds like a "specification bug" that RVO is not allowed when std::move is used. Or is there a deeper reason for this?
Doing so would require the standard to give std::move special treatment, which sucks. The way it's specified right now let's you write your own std::move and call it rvalue_cast() if you wish.
Why is that an important design criterion?
Not everyone uses the standard libraries for lots of complicated reasons.
> Doing so would require the standard to give std::move special treatment

Does it? Why change the behavior specifically for std::move instead of just allowing any rvalue reference to be elided when possible?

To elide a return copy/move, the compiler has to know at compile time exactly which local variable is going to be returned, so that it can arrange to have that local variable constructed directly into the return location, thus avoiding the need to copy/move it later.

So to elide a copy/move from an rvalue reference, the compiler would have to know at compile time that the rvalue reference always points to some particular local variable.

In theory a compiler could look into the inline definition of std::move() and determine that it always returns a reference to its parameter, but that's a rather involved optimization for the spec to require of all implementations.

There's never a place where RVO could occur where `return std::move(...)` is a sensible thing to write, so it's not actually a problem in practice. If you're returning a local you don't have to explicitly move from it, and if you're returning a global or member variable RVO can't occur.
Someone correct me if I am wrong but isn't that last snippet where he returns std::move(localObj) essentially returning an rvalue-ref to a local object? That's like a big no-no because the local object destructs after foo(1) returns.
It's definitely an appropriate use of std::move. When the local object destructs std::move has already ripped its guts out and put them in the object up the stack. The local object is just an empty shell at destruction.
It's a safe, but inappropriate, use of std::move, which is kind of the point of the article ;)
The 'object up the stack' is an rvalue reference. While we've successfully returned a reference into the caller's stack frame (RVO), the object it referred to has already been destroyed. That's why you see 'constructor, destructor' before 'move constructor'.

I'm struggling to see how it's an appropriate use of std::move in any case where you actually use the rvalue reference!

It'd be a problem if the function returned BigObject&&, but since it returns BigObject the move happens inside the function body which should be fine (modulo the issues raised in the article).
In the last snippet the function returns BigObject&&.
I'm not sure if he updated the article since you read it, but he does have a note about that:

> Note: We should not use this way in the real development, because it is a reference to a local object. Here just show how to make RVO happened.

Oh my goodness. I'll blame that one on recovering from a bad cold.
(comment deleted)
Presumably this is why his final output consisted of

  constructor.
  destructor.
  move constructor
  destructor.
i.e. the data destructed before it was moved.
Another way to frame this discussion would be in terms of xvalues. The standard defines an xvalue as an "eXpiring value", which is value near the end of it's lifetime [1,2].

  struct X {};

  void f() {
    X x;
    return x; // elided
  }

  void f(X x) {
    return x; // elided
  }
Perhaps not as intuitive as the first copy elision is the second, but accepting a parameter by value and returning it also enables copy elision because it is an xvalue at the end of the scope. A move is equally detrimental to performance in both cases.

[1] http://eel.is/c++draft/basic.lval#xvalue [2] http://stackoverflow.com/q/3601602/1170277

> accepting a parameter by value and returning it also enables copy elision

No it doesn't. Parameters are constructed by the caller (in order to allow eliding the parameter copy, if the argument is a temporary anyway), therefore returning a parameter cannot be elided because that would have required the caller to know to construct the parameter in the return value slot.

If you actually test your code, you'll see that in the `f(X x)` case, the return is not elided.

With that said, in C++11, the return is automatically by move, not by copy. Any "return x;" where x is a local variable (including parameters) will be by move. But prior to C++11, it would have been a copy.

This is IMO the biggest problem with relying on RVO for anything: people rarely understand all of the rules around it, and may reasonably assume that RVO will kick in in cases where it doesn't. It's even harder if you have a function with a few branches and multiple returns -- in some cases, even when the branching happens before the return variable is declared, the compiler will fail to RVO it. Luckily C++11's "returning a local variable is automatically by move" rule covers you in most such cases.

FWIW, I highly recommend following Rust's rule: Only trivial, shallow types can be implicitly copied (i.e. only cases where the copy is a simple memcpy()). Anything else can only be moved, but is free to define a `clone()` method for explicit copies. If your type doesn't have a copy constructor then you can't accidentally call it. :)

> No it doesn't. Parameters are constructed by the caller (in order to allow eliding the parameter copy, if the argument is a temporary anyway), therefore returning a parameter cannot be elided because that would have required the caller to know to construct the parameter in the return value slot.

Sorry, I confounded two things here. What I meant to say is that "x" is an xvalue in both scenarios and you wouldn't write std::move(x) in either case. For the overload f(X) you'd always incur a move, whereas for the overload f() you'd always get a copy elision.

Concretely, I've looked at the assembly of this code:

  #include <string>
  
  struct X {
    std::string str;
  };
  
  auto f(X x) -> X {
    return x;
  }
  
  auto f() -> X {
    X x;
    return x;
  }
  
  auto main() -> int {
    X x = f();
    auto y = f(x);
  }
When compiling with

  c++ -g -std=c++14 test.cc && otool -tV a.out | c++filt
the assembly shows that indeed shows that f(X) invokes the move constructor, whereas the copy is elided for f().
Remember, the most important thing to take away from RVO is that copy and move constructors should not have side-effects that change the behaviour of your program (except for throwing an exception if they fail, and move constructors should be designed never to throw)
The whole point of move constructors are observable side-effects.

Furthermore move constructors often have to throw an exception due to the fact that moving an object should leave the original object in a valid state. Often times leaving the original object in a valid state requires involves performing an operation that could potentially fail. In the case of std::map and std::list, a conforming implementation may have to allocate memory in order for the original object to be in a valid state.

> The whole point of move constructors are observable side-effects.

Nonsense.

> Furthermore move constructors often have to throw an exception due to the fact that moving an object should leave the original object in a valid state.

Whether or not move constructors should leave objects in a 'valid state', whatever you mean by that, is hotly contested. Personally I'm of the opinion the only valid operations on a moved-from object should be to move-assign to it (otherwise literally every mutating algorithm is broken) and, obviously, to destroy it.

> Often times leaving the original object in a valid state requires involves performing an operation that could potentially fail.

Which is exactly why I'm of the above opinion. Making move non-throwing is more or less always trivial and cheap, whereas doing so while ensuring a moved-from object has a valid and useful state, where all class invariants have been maintained, can be expensive.

You should definitely favour a noexcept move constructor that results in an unspecified state over a implementing a throwing move. Just don't do it. If your move constructor throws then doing anything useful with your class becomes very expensive (because classes like std::vector will start copying your objects to move them around).

> In the case of std::map and std::list, a conforming implementation may have to allocate memory in order for the original object to be in a valid state.

This is just wrong. Moving a std::list will not throw provided the associated memory allocator is noexcept move constructible (and std::allocator is). The same is true of std::map.

std::list's move constructor is deliberately not specified to be noexcept even when the allocator's is, and in Dinkumware's implementation it actually can throw, since it allocates a new end sentinel for the moved-from list.
Constructors may need to allocate a sentinel, I see no reason for the move constructor to do so. I'd also question the performance benefits of having a sentinel (basically avoiding additional null pointer checks) has on performance given todays relative cache penalties.
Instead of trying to reason about the copying and moving of huge objects, I give my brain a break by using std::unique_ptr<HugeObject>. unique_ptr is moveable, cannot accidentally be copied (because its copy constructor is private), and is trivially small. unique_ptr also gets around the problem where the compiler can't figure out which value is being RVO'd, because you can do this:

  std::unique_ptr<BigObject> Blerg(yourmom) {
    std::unique_ptr<BigObject> big_object_ptr;
    BigObject foo, bar;
    if (2 > yourmom) {  
      big_object_ptr.reset(foo);
    } else {
      big_object_ptr.reset(bar);
    } 

    return std::move(big_object_ptr);
  }
This isn't valid code but it's pretty close.
This doesn't help to solve the original problem of avoiding the copy. Your calls to

    big_object_ptr.reset(...)
will cause the BigObject copy constructor to be called. The object pointed to by unique_ptr does not have the same storage as the objects "foo" and "bar". At the very least, you need to do

    big_object_ptr.reset(std::move(foo));
which will cause foo to be moved into the unique_ptr's storage.

In code like this, you should just use make_unique, in which case the BigObject is constructed in place, and unique_ptr can be RVO'ed:

    if (...) {
        return make_unique<BigObject>(...);
    } else ...
Yeah I realized after the fact that the example is pointless, but I was trying to adhere to the original post, which is also pointless. Why would you construct two BigObject when you know you will discard one of them unused? It's dumb.
Your code implies the use of heap memory, with values you can work only on the stack.
You can actually just do return big_object_ptr; Instead of the std::move. The compiler will complain if it can't do RVO because unique_ptr prohibits a copy, so you're safe :)
RVO is flawed because the compiler may(!) elide function calls. The compiler cannot know if those function calls are semantically superfluous or not.

'move' is flawed because if blurs the object id == object address equality inherited from C.

Both, RVO and 'move' are workarounds for language deficits - workarounds gone wrong.

A copy constructor that cannot be elided is a broken copy constructor.
This may be true for simple structs but is definitely wrong as a general statement. Compilers cannot anticipate semantics.
It is true for all copy constructors in C++ since the standard allows compilers to perform copy elision.
Copy elision + move semantics can be quite confusing at first in C++. For example at times explicitly calling move can be less efficient than implicitly relying on copy elision.

Another good read: http://en.cppreference.com/w/cpp/language/copy_elision

> explicitly calling move can be less efficient than implicitly relying on copy elision

Sure, in the sense that non-zero is greater than zero. But move constructors are almost always basically a memcpy() sometimes followed by some zeroing of the old data, never heap allocation nor expensive atomic ops. IMO it's not worth worrying about. Much better to accidentally invoke a redundant move because you specified std::move() when you didn't have to than to accidentally invoke a redundant copy because you thought RVO would kick in but it didn't.

That may be, though code becomes more cluttered with moves as well.