Question

I've been reading about rvalue references and std::move, and I have a performance question. For the following code:

template<typename T>
void DoSomething()
{
  T x = foo();
  bar(std::move(x));
}

Would it be better, from a performance perspective, to rewrite this using an rvalue reference, like so?

template<typename T>
void DoSomething()
{
  T&& x = foo();
  bar(x);
}

If I understand rvalue references correctly, this will simply act as if bar(foo()) had been called, as pointed out by commenters. But the intermediate value may be needed; is it useful to do this?

Was it helpful?

Solution

The best way to use rvalue references / move semantics, is for the most part, don't try too hard. Return function locals by value. And construct them by value from such functions:

X foo();

template<typename T>
void DoSomething()
{
  T x = foo();
  ...

If you are the author of class X, then you probably want to ensure that X has efficient move construction and move assignment. If the copy constructor and copy assignment of X are already efficient, there is literally nothing more to do:

class X
{
     int data_;
public:
     // copy members just copy data_
};

If you find yourself allocating memory in X's copy constructor, and or copy assignment, then you should consider writing a move constructor and move assignment operator. Do your best to make them noexcept.

Although there are always exceptions to the rule, in general:

  1. Don't return by reference, either lvalue or rvalue reference, unless you want the client to have direct access to a non-function-local variable.

  2. Don't catch a return by reference, lvalue or rvalue. Yes, there are cases where it might save you something, without getting you into trouble. Don't even think about doing it as a rule. Only do something like this if your performance testing is highly motivating you to do so.

  3. All of the things you (hopefully) learned not to do with lvalue references are still just as dangerous to do with rvalue references (such as returning a dangling reference from a function).

  4. First program for correctness. This includes writing extensive and easy-to-run unit tests covering common and corner cases of your API. Then start optimizing. When you start out trying to write extremely optimized code (e.g. catching a return by reference) before you have a correct and well-tested solution, one inevitably writes code that is so brittle that the first bug fix that comes along just breaks the code further, but often in very subtle ways. This is a lesson that even experienced programmers (including myself) have to learn over and over.

  5. In spite of what I say in (4), even on the first write, keep an eye on O(N) performance. I.e. if your first try is so grossly slow due to basic overall design deficiencies or poor algorithms that it can't perform its basic function in a reasonable time, then you need more than new code, you need a new design. In this bullet I am not talking about things like whether or not you've caught a return with an rvalue reference. I'm talking about whether or not you've created an O(N^2) algorithm, when O(N log N), or O(N) could've done the job. This is all too easy to do when the job that needs to get done is non-trivial, or when the code is so complicated that you can't tell what is going on.

OTHER TIPS

Here std::move does not help.
As you have made a copy of the object (though elision may help).

template<typename T>
void DoSomething()
{
  T x = foo();
  bar(std::move(x));
}

Here rvalue reference is not helping
As the variable x is a named object (and thus not an rvalue reference (anymore)).

template<typename T>
void DoSomething()
{
  T&& x = foo();
  bar(x);
}

Best to use:

template<typename T>
void DoSomething()
{
  bar(foo());
}

But if you must use it locally:

template<typename T>
void DoSomething()
{
  T&& x = foo();
  //  Do stuff with x
  bar(std::move(x));
}

Though I am not 100% sure about the above and would love some feedback.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top