Move semantics is there for situations when you have to copy data, i.e. when there's no way around it. The data has to "appear" in a new location, in a new object. I.e by design, the physical relocation/re-creation of data absolutely has to take place. Normal references don't cover this in any way. For example, when you add an element to a std::vector
the element's value supplied by you also has to be appear in the new vector's element somehow.
Most of the time in classic C++ this was achieved by unconditionally copying data to the new location. However, it was rather obvious that in some cases we didn't really care about the fate of the original object, from which that data was copied - the original object was destroyed immediately after copying anyway. Naturally, it presented a rather obvious and huge optimization opportunity: since we didn't care about the original object, we could simply "steal" (move) data from it, place that data into the new location and then destroy the (now empty) original. This is significantly more efficient that meticulously "cloning" (copying) the data and destroying the original.
We used to take advantage of this optimization opportunity even in classic C++. But we had to do it manually, without support from the core language. Move semantics and rvalue references is how this support was introduced into the core language, this opening this optimization opportunity to the compiler itself.
In other words, move semantics does not "eliminate copying". It optimizes copying by taking advantage of the fact that the sequence
- Copy "heavy"
src
to dst
- Destroy "heavy"
src
can be equivalently replaced by a much more efficient sequence
- Move "heavy"
src
do dst
, making src
"empty"
- Destroy "empty"
src