I am basically trying to figure out, is the whole \"move semantics\" concept something brand new, or it is just making existing code simpler to implement? I am always interested
In the Turing Tar Pit, there is nothing new under the sun. Everything that move semantics does, can be done without move semantics -- it just takes a lot more code, and is a lot more fragile.
What move semantics does is takes a particular common pattern that massively increases efficiency and safety in a number of situations, and embeds it in the language.
It increases efficiency in obvious ways. Moving, be it via swap
or move
construction, is much faster for many data types than copying. You can create special interfaces to indicate when things can be moved from: but honestly people didn't do that. With move semantics, it becomes relatively easy to do. Compare the cost of moving a std::vector
to copying it -- move
takes roughly copying 3 pointers, while copying requires a heap allocation, copying every element in the container, and creating 3 pointers.
Even more so, compare reserve
on a move-aware std::vector
to a copy-only aware one: suppose you have a std::vector
of std::vector
. In C++03, that was performance suicide if you didn't know the dimensions of every component ahead of time -- in C++11, move semantics makes it as smooth as silk, because it is no longer repeatedly copying the sub-vector
s whenever the outer vector resizes.
Move semantics makes every "pImpl
pattern" type to have blazing fast performance, while means you can start having complex objects that behave like values instead of having to deal with and manage pointers to them.
On top of these performance gains, and opening up complex-class-as-value, move semantics also open up a whole host of safety measures, and allow doing some things that where not very practical before.
std::unique_ptr
is a replacement for std::auto_ptr
. They both do roughly the same thing, but std::auto_ptr
treated copies as moves. This made std::auto_ptr
ridiculously dangerous to use in practice. Meanwhile, std::unique_ptr
just works. It represents unique ownership of some resource extremely well, and transfer of ownership can happen easily and smoothly.
You know the problem whereby you take a foo*
in an interface, and sometimes it means "this interface is taking ownership of the object" and sometimes it means "this interface just wants to be able to modify this object remotely", and you have to delve into API documentation and sometimes source code to figure out which?
std::unique_ptr
actually solves this problem -- interfaces that want to take onwership can now take a std::unique_ptr
, and the transfer of ownership is obvious at both the API level and in the code that calls the interface. std::unique_ptr
is an auto_ptr
that just works, and has the unsafe portions removed, and replaced with move semantics. And it does all of this with nearly perfect efficiency.
std::unique_ptr
is a transferable RAII representation of resource whose value is represented by a pointer.
After you write make_unique
, unless you are writing really low level code, it is probably a good idea to never call new
directly again. Move semantics basically have made new
obsolete.
Other RAII representations are often non-copyable. A port, a print session, an interaction with a physical device -- all of these are resources for whom "copy" doesn't make much sense. Most every one of them can be easily modified to support move semantics, which opens up a whole host of freedom in dealing with these variables.
Move semantics also allows you to put your return values in the return part of a function. The pattern of taking return values by reference (and documenting "this one is out-only, this one is in/out", or failing to do so) can be somewhat replaced by returning your data.
So instead of void fill_vec( std::vector
, you have std::vector
. This even works with multiple return values -- std::tuple< std::vector, std::set, bool > get_stuff()
can be called, and you can load your data into local variables efficiently via std::tie( my_vec, my_set, my_bool ) = get_stuff()
.
Output parameters can be semantically output-only, with very little overhead (the above, in a worst case, costs 8 pointer and 2 bool
copies, regardless of how much data we have in those containers -- and that overhead can be as little as 0 pointer and 0 bool
copies with a bit more work), because of move semantics.