18

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.

VF1
  • 1,594
  • 2
  • 11
  • 31
  • 9
    Why have multiplication? You can do the same thing with addition. – Pete Becker Nov 30 '13 at 21:15
  • 8
    Note: The second reason for move semantics is that types like `std::unique_ptr` are possible to implement. (without it, we would still have `std::auto_ptr` suckage) – milleniumbug Nov 30 '13 at 21:17
  • @PeteBecker I don't think that's a valid analogy. Multiplication is necessary because to do the same with addition we would need size-dependent repetitions of addition, whereas in this example, as I think I demonstrated, all move semantics seems to do is change where the pointer swap occurs. – VF1 Nov 30 '13 at 21:17
  • 2
    @VF1 we could do multiplication with addition and a while loop couldn't we? – uk4321 Nov 30 '13 at 21:26
  • I would leave your class value-based if possible. If someone profiles and sees copying these around is taking too long, they can opt-in to dynamically allocating your class and swapping that around. By doing that for them, you're always paying for the allocation even if it's not needed. – GManNickG Nov 30 '13 at 21:29
  • @GManNickG - So then it seems to me that move semantics is an optimization that is limited to classes with members that already have dynamically allocated data (or sub-members), like ones with `vector` or `string`? – VF1 Nov 30 '13 at 21:33
  • 1
    One main purpose of classes is to *manage resources*. They *simplify* the pointer thing you're showing here by encapsulating it. Of course you can make a value-semantics type with an large array member; then you can either use it directly or put it into a class which manages its creation and deletion on the free store. For the latter, move semantics is useful. – dyp Nov 30 '13 at 21:37
  • 1
    Isn't this a bit like asking: Why have C++ when you can do it all with C too? – hyde Dec 03 '13 at 19:22
  • @hyde So? As long as I (and potential future users) gain a deeper understanding of what exactly a certain feature of the language does and why it is there, that question is absolutely worth asking. – VF1 Dec 03 '13 at 21:32
  • @VF1 Maybe it is worth asking (I did not down vote, if you are implying that), but offering pointers, especially raw pointers, as an alternative to cover all use cases... That seems very apples and oranges, misguided, C vs C++. But I trust the answers have cleared this up :-) – hyde Dec 04 '13 at 04:51

4 Answers4

21

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:

Move semantics is mostly about performance optimization: the ability to move an expensive object from one address in memory to another, while pilfering resources of the source in order to construct the target with minimum expense.

Move semantics already exists in the current language and library to a certain extent:

  • copy constructor elision in some contexts
  • auto_ptr "copy"
  • list::splice
  • swap on containers

All of these operations involve transferring resources from one object (location) to another (at least conceptually). What is lacking is uniform syntax and semantics to enable generic code to move arbitrary objects (just as generic code today can copy arbitrary objects). There are several places in the standard library that would greatly benefit from the ability to move objects instead of copy them (to be discussed in depth below).

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 use swap because that would be too expensive when the value_type is int. And one can't use copy assignment as that would be too expensive when value_type is A (the OP's A). Well, one could use copy assignment, after all we did in C++98/03, but it is ridiculously expensive.

shouldn't all non-primitive type members be pointers to speed up movement

This would be horribly expensive when the member type is complex<double>. Might as well color it Java.

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
20

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 years a while without them. They are simply a better way to represent certain concepts, and an optimization.

uk4321
  • 1,028
  • 8
  • 18
  • Could you please give an example where move semantics optimizes for value types without dynamic allocation? Even if the allocation happens behind the scenes, it still has to happen. – VF1 Nov 30 '13 at 21:11
  • 7
    @VF1 without move semantics you have twice the dynamic allocation. Also not only does it matter for dynamic allocation types, but also for types that represent non-copyable resources, like a socket. – uk4321 Nov 30 '13 at 21:13
  • What is not exception-safe? Wouldn't `unique_ptr` not allieviate the issue? Regarding double allocation, this would only be in circumstances in which the class itself would use dynamically allocated storage. I suppose that is more expensive, but not by much... – VF1 Nov 30 '13 at 21:13
  • 10
    @VF1 if you use `unique_ptr` without move semantics you'd get `auto_ptr`. Look up the numerous articles about the problems with auto_ptr. – uk4321 Nov 30 '13 at 21:14
  • `C++ has existed for 40 years` - That doesn't sounds right to me... The first ISO C++ standard was ratified 1998 - about 15 years ago: not 40 years! – Constantin Nov 30 '13 at 21:29
  • @Constantin I didn't mean standardized. But yeah, 40 is a little too long, fixed. – uk4321 Nov 30 '13 at 21:31
  • 2
    1983-2013: 30 years :) – user541686 Nov 30 '13 at 22:00
  • 1983 was only the year it was renamed C++. It existed before that. And there were certainly even professional C++ programmers before it was an ISO standard. – Tim Seguine Dec 03 '13 at 12:15
16

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?

Your second example gives one very good reason why move semantics is a good thing:

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;
}

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.

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.

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.

is it really worth all the trouble with the double ampersands?

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.

Ali
  • 56,466
  • 29
  • 168
  • 265
  • 4
    +1 Ownership is one of the most important reasons because raw-pointers shouldm't be used as return values of functions. – Manu343726 Dec 03 '13 at 12:33
  • +1. I understand it is not very orthodox to take ownership of a pointer from a function, but I was looking for reasons other than style (which were mentioned - namely, safety and the extra allocation of the pointer to the object itself). To be honest, though, the allocation does not seem to be that expensive, but the safety and encapsulation reasons seem to be convincing. – VF1 Dec 04 '13 at 00:02
  • @VF1 I am glad you like the answer. – Ali Dec 04 '13 at 00:36
8

Your string example is great. The short string optimization means that short std::strings do not exist in the free store: instead they exist in automatic storage.

The new/delete version means that you force every std::string into the free store. The move 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. You move when you do not want duplicate state, and copy otherwise. Nearly normal types that cannot be copied can expose move only (unique_ptr), others can optimize for it (shared_ptr). Data stored in containers, like std::vector, can now include abnormal types because it is move aware. The std::vector of std::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.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524