Move semantics aren't necessarily all that big of an improvement when you're returning a value -- and when/if you use a shared_ptr
(or something similar) you're probably prematurely pessimizing. In reality, nearly all reasonably modern compilers do what's called Return Value Optimization (RVO) and Named Return Value Optimization (NRVO). This means that when you're returning a value, instead of actually copying the value at all, they simply pass a hidden pointer/reference to where the value's going to be assigned after the return, and the function uses that to create the value where it's going to end up. The C++ standard includes special provisions to allow this, so even if (for example) your copy constructor has visible side effects, it's not required to use the copy constructor to return the value. For example:
#include <vector>
#include <numeric>
#include <iostream>
#include <stdlib.h>
#include <algorithm>
#include <iterator>
class X {
std::vector<int> a;
public:
X() {
std::generate_n(std::back_inserter(a), 32767, ::rand);
}
X(X const &x) {
a = x.a;
std::cout << "Copy ctor invoked\n";
}
int sum() { return std::accumulate(a.begin(), a.end(), 0); }
};
X func() {
return X();
}
int main() {
X x = func();
std::cout << "sum = " << x.sum();
return 0;
};
The basic idea here is fairly simple: create a class with enough content we'd rather avoid copying it, if possible (the std::vector
we fill with 32767 random ints). We have an explicit copy ctor that'll show us when/if it gets copied. We also have a little more code to do something with the random values in the object, so the optimizer won't (at least easily) eliminate everything about the class just because it does nothing.
We then have some code to return one of these objects from a function, and then use the summing to ensure the object is really created, not just ignored completely. When we run it, at least with most recent/modern compilers, we find that the copy constructor we wrote never runs at all -- and yes, I'm pretty sure that even a fast copy with a shared_ptr
is still slower than doing no copying at all.
Moving allows you to do a fair number of things you simply couldn't do (directly) without them. Consider the "merge" part of an external merge sort -- you have, say, 8 files you're going to merge together. Ideally you'd like to put all 8 of those files into a vector
-- but since vector
(as of C++03) needs to be able to copy elements, and ifstream
s can't be copied, you're stuck with some unique_ptr
/shared_ptr
, or something on that order to be able to put them in a vector. Note that even if (for example) we reserve
space in the vector
so we're sure our ifstream
s will never really be copied, the compiler won't know that, so the code won't compile even though we know the copy constructor will never be used anyway.
Even though it still can't be copied, in C++11 an ifstream
can be moved. In this case, the objects probably won't ever be moved, but the fact that they could be if necessary keeps the compiler happy, so we can put our ifstream
objects in a vector
directly, without any smart pointer hacks.
A vector that does expand is a pretty decent example of a time that move semantics really can be/are useful though. In this case, RVO/NRVO won't help, because we're not dealing with the return value from a function (or anything very similar). We have one vector holding some objects, and we want to move those objects into a new, larger chunk of memory.
In C++03, that was done by creating copies of the objects in the new memory, then destroying the old objects in the old memory. Making all those copies just to throw away the old ones, however, was quite a waste of time. In C++11, you can expect them to be moved instead. This typically lets us, in essence, do a shallow copy instead of a (generally much slower) deep copy. In other words, with a string or vector (for only a couple of examples) we just copy the pointer(s) in the objects, instead of making copies of all the data those pointers refer to.
shared_ptr
just for the sake of fast copying) and if move semantics can achieve the same with nearly no coding-, semantics- and cleanliness-penalty.