Question

The typical reasoning behind the introduction of rvalue references into C++ is to eliminate (optimize) superfluous copying during the evaluation of complex C++ expressions.

However, there are two compiler optimization techniques for C++98/C++03, which basically serve the same purpose, namely

Is there any real-world use case, when the above techniques (with appropriately written code) fail to eliminate superfluous copying, but rvalue references can succeed?

Was it helpful?

Solution 2

rvalue references are designed with two aims in mind:

  • move semantics
  • perfect forwarding

Move semantics

One purpose of move semantics, in general, is an optimization of copy. Where you can copy, you can just move the resources held by the object.

std::string str("The quick brown fox jumps over the lazy dog.");
std::cout << str;      // Do something
func(std::move(str));  // You do not need str in the subsequent lines, and so move

Also, the compiler can optimize the return value of a function, where RVO may not be applicable

std::vector<std::string> read_lines_from_file(const std::string& filename);

auto lines = read_lines_from_file("file1.txt");  // Can possibly be optimized with RVO
// Do something with lines
lines = read_lines_from_file("file2.txt");  // RVO isn't applicable, but move is
// Do something with lines

If you implement move semantics in your classes, chances are you will experience a significant performance boost when you are putting them in containers. Consider std::vector. Whenever you modify the elements of an std::vector, at some time, some reallocations and element shifting would happen. In C++98, elements are mostly copied around[1], which is unnecessary, as there is logically only one representation of the object that is needed at a single point of time. With move semantics, containers like std::vector can just move the objects around, eliminating extra copies and therefore boost the performance of your program.

One very important use of move semantics is not just for optimization but for implementing unique ownership semantics. One very good example is std::unique_ptr in which it can only be moved around (it cannot be copied), therefore you are guaranteed (with proper use) that there is only one std::unique_ptr that is managing the contained pointer or resource.


Perfect forwarding

Perfect forwarding uses rvalue references and special template deduction rules for us to be able to implement perfectly forwarding functions, that is, functions that can forward arguments correctly and without making any copies.

Examples are std::make_shared and the upcoming std::make_unique. Also are the new emplace* methods of containers, one that you will find very useful and convenient (not mentioning that it would also be very efficient).


This answer provides a very good and thorough explanation of rvalue references with respect to move semantics and perfect forwarding.


[1] One thing I heard from one of Bjarne Stroustrup's talk[source needed] is that containers typically implement some ad-hoc move semantics. Therefore, as he has said, you may not experience that much of a speed-up.

OTHER TIPS

If you need one single argument why rvalue references are essential, it's unique_ptr. It's the archetype of resource managing SBRM classes. Resources are by definition not copyable, but the managers should be movable

An owning container of heterogeneous, related objects is straight-forward to implement as a container of unique_ptrs of the base class, and it is a very common programming idiom. Before rvalue references, it was never quite possible to implement this cleanly in "native C++", and would always require manual housekeeping.

Threads and locks are further examples of resources.

In a nutshell, optimization is just one part of the story, but movability is a quality in its own right that just wasn't very easy to address before rvalue references.

There are two reasons for using rvalue references and move semantics.

  • For most programmers, the most important (or even the only reason which affects their code) is the possibility of supporting "limited copy". (I'm sorry, I don't know a good name for this.) Like the iostream classes, for example: you don't want to support copy, per se, but you do want to allow construction in a separate function. A typical example might be a log stream: you want to return the appropriate log stream from a function, but you only want a single instance, whose destructor will ensure that the logging actions (i.e. sending email, posting to syslog, etc.) are atomic.

  • At times, it can also be used for optimization; you mention things like return value optimization and copy elision, but they don't always apply, especially with assignment. Generally, in such cases, you would ignore rvalue references and move until the profiler said there was a problem, but if you're implementing a library, you might not have this luxury.

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