Let me preface by saying that I have read some of the many questions already asked regarding move semantics. This question is not about how to use move semantics, it is asking what the purpose of it is - if I am not mistaken, I do not see why move semantics is needed.
Background
I was implementing a heavy class, which, for the purposes of this question, looked something like this:
class B;
class A
{
private:
std::array<B, 1000> b;
public:
// ...
}
When it came time to make a move assignment operator, I realized that I could significantly optimize the process by changing the b
member to std::array<B, 1000> *b;
- then movement could just be a deletion and pointer swap.
This lead me to the following thought: now, shouldn't all non-primitive type members be pointers to speed up movement (corrected below [1] [2]) (there is a case to be made for cases where memory should not be dynamically allocated, but in these cases optimizing movement is not an issue since there is no way to do so)?
Here is where I had the following realization - why create a class A
which really just houses a pointer b
so swapping later is easier when I can simply make a pointer to the entire A
class itself. Clearly, if a client expects movement to be significantly faster than copying, the client should be OK with dynamic memory allocation. But in this case, why does the client not just dynamically allocate the whole A
class?
The Question
Can't the client already take advantage of pointers to do everything move semantics gives us? If so, then what is the purpose of move semantics?
Move semantics:
std::string f()
{
std::string s("some long string");
return s;
}
int main()
{
// super-fast pointer swap!
std::string a = f();
return 0;
}
Pointers:
std::string *f()
{
std::string *s = new std::string("some long string");
return s;
}
int main()
{
// still super-fast pointer swap!
std::string *a = f();
delete a;
return 0;
}
And here's the strong assignment that everyone says is so great:
template<typename T>
T& strong_assign(T *&t1, T *&t2)
{
delete t1;
// super-fast pointer swap!
t1 = t2;
t2 = nullptr;
return *t1;
}
#define rvalue_strong_assign(a, b) (auto ___##b = b, strong_assign(a, &___##b))
Fine - the latter in both examples may be considered "bad style" - whatever that means - but is it really worth all the trouble with the double ampersands? If an exception might be thrown before delete a
is called, that's still not a real problem - just make a guard or use unique_ptr
.
Edit [1] I just realized this wouldn't be necessary with classes such as std::vector
which use dynamic memory allocation themselves and have efficient move methods. This just invalidates a thought I had - the question below still stands.
Edit [2] As mentioned in the discussion in the comments and answers below this whole point is pretty much moot. One should use value semantics as much as possible to avoid allocation overhead since the client can always move the whole thing to the heap if needed.
Your example gives it away: your code is not exception-safe, and it makes use of the free-store (twice), which can be nontrivial. To use pointers, in many/most situations you have to allocate stuff on the free store, which is much slower than automatic storage, and does not allow for RAII.
They also let you more efficiently represent non-copyable resources, like sockets.
Move semantics aren't strictly necessary, as you can see that C++ has existed for
40 yearsa while without them. They are simply a better way to represent certain concepts, and an optimization.I thoroughly enjoyed all the answers and comments! And I agree with all of them. I just wanted to stick in one more motivation that no one has yet mentioned. This comes from N1377:
I.e. in generic code such as
vector::erase
, one needs a single unified syntax to move values to plug the hole left by the erased valued. One can't useswap
because that would be too expensive when thevalue_type
isint
. And one can't use copy assignment as that would be too expensive whenvalue_type
isA
(the OP'sA
). Well, one could use copy assignment, after all we did in C++98/03, but it is ridiculously expensive.This would be horribly expensive when the member type is
complex<double>
. Might as well color it Java.Your second example gives one very good reason why move semantics is a good thing:
Here, the client has to examine the implementation to figure out who is responsible for deleting the pointer. With move semantics, this ownership issue won't even come up.
Again, the ugly ownership issue shows up if you don't use move semantics. By the way, how would you implement
unique_ptr
without move semantics?I know about
auto_ptr
and there are good reasons why it is now deprecated.True, it takes some time to get used to it. After you are familiar and comfortable with it, you will be wondering how you could live without move semantics.
Your string example is great. The short string optimization means that short
std::string
s do not exist in the free store: instead they exist in automatic storage.The
new
/delete
version means that you force everystd::string
into the free store. Themove
version only puts large strings into the free store, and small strings stay (and are possibly copied) in automatic storage.On top of that your pointer version lacks exception safety, as it has non-RAII resource handles. Even if you do not use exceptions, naked pointer resource owners basically forces single exit point control flow to manage cleanup. On top of that, use of naked pointer ownership leads to resource leaks and dangling pointers.
So the naked pointer version is worse in piles of ways.
move
semantics means you can treat complex objects as normal values. Youmove
when you do not want duplicate state, andcopy
otherwise. Nearly normal types that cannot be copied can exposemove
only (unique_ptr
), others can optimize for it (shared_ptr
). Data stored in containers, likestd::vector
, can now include abnormal types because it ismove
aware. Thestd::vector
ofstd::vector
goes from ridiculously inefficient and hard to use to easy and fast at the stroke of a standard version.Pointers place the resource management overhead into the clients, while good C++11 classes handle that problem for you.
move
semantics makes this both easier to maintain, and far less error prone.