This week-end I looked at C++ rvalue references and move semantics. Here’s the code, the good stuff is in matrix.h.

Rvalue references seem useful, but *way* too treacherous. Any programming construct that raises doubts on the nature of objective reality and evokes associations with quantum physics (‘reference collapse’ ~ ‘wave function collapse’) is probably too complicated for its own good. But… maybe you don’t believe me? The great Scott Meyers explains it beautifully in his channel 9 presentation on rvalue references. I was unable to get it right on my own before I watched that.

Rvalue references can be used to reduce unnecessary data copying. I was able to create a matrix class that passes matrices by value, but avoids actually duplicating the data whenever possible. This is great, but let’s see the laundry list of gotchas I ran into:

- Variable of type
`T&&`

cannot be passed to a function that accepts`T&&`

(oops!). - You fix it by using
`forward(v)`

or`move(v)`

, but the difference between the two is confusing. - When you see
`T&&`

in code, it may not in fact be an rvalue reference. - If you are not careful, you may silently get copy behavior instead of move behavior.
- Overloads may not work the way you expect.

But let’s start with the good stuff.

### What you can do with rvalue references.

I created a class `matrix`

with `operator+`

: complete source at https://github.com/ikriv/cpp-move-semantics.

Rvalue references are traditionally used to optimizing copy constructors and assignment, but they can be as useful in case of operator +. Traditional implementation of such operator for matrices would look something like this:

matrix operator+(matrix const& a, matrix const& b) { matrix result = a; result += b; return result; }

If we have an experssion like `matrix result = a+f(b)+f(c)+d;`

the compiler will have no choice, but to create a bunch of temporary matrices and copy a lot of data around. We can significantly reduce the amount of copying by adding rvalue-based overloads of operator +() that modify an rvalue parameter “in place” instead of allocating a new matrix. The idea is that rvalue is by definition inaccessible outside the expression, and thus there is no harm in modifying it.

matrix operator+(matrix&& a, matrix const& b) // rvalue+lvalue { return std::move(a += b); } matrix operator+(matrix const& a, matrix&& b) // lvalue+rvalue { return std::move(b += a); } matrix operator+(matrix&& left, matrix&& right) // rvalue+rvalue { return std::move(a += b); }

This does improve the performance dramatically. With the move semantics my code allocates 8 matrices. All rvalue reference overloads are conditionally compiled via #define MOVE_SEMANTICS. If I turn that off, the number of matrices allocated jumps to 12 in release mode and to 24 in debug mode.

So, rvalue references *are *useful, but they are still a minefield. You must really want those optimizations, and even then if you are not careful, you may not get what you expected. To be continued…

Permalink

Permalink