27

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 reducing the number of times I call copy/constructors but I usually pass objects through using reference (and possibly const) and ensure I always use initialiser lists. With this in mind (and having looked at the whole ugly && syntax) I wonder if it is worth adopting these principles or simply coding as I already do? Is anything new being done here, or is it just "easier" syntactic sugar for what I already do?

user997112
  • 29,025
  • 43
  • 182
  • 361
  • 8
    Your function can't return a reference to an object on stack, so that would be one example of where move constructors can come in handy. You still return it by value, but when you assign the result to a variable, you use move constructor instead of copy. – riv May 29 '13 at 21:01
  • 8
    There is *a lot* new being done here. – Andy Prowl May 29 '13 at 21:02
  • 5
    It is possible to get the same resulting asm (or even better) using C++03 or even plain C, without rvalue references. But rvalue references remove (most of) the performance cost of using an extremely natural syntax. – Marc Glisse May 29 '13 at 21:08
  • 2
    Any answer claiming there is something "new" here must also explain why Boost.RV doesn't do the same thing as move semantics. – user541686 May 30 '13 at 03:27
  • 3
    @Mehrdad: As excellent as `Boost.Move` is, C++11 rvalue references have a number of advantages. 1) `Boost.Move` violates strict aliasing, and so any code that uses is has undefined behaviour. 2) C++11 rvalue reference variables are more polished: `&&` variables cannot be implicitly moved from, but `boost::rv` variables can be. C++11 can implicitly move in places that require explicit moves for Boost.Move (such as returning local variables). Perfect forwarding. 3) Being part of the standard means that move semantics can be used in a uniform/generic way throughout the standard library. – Mankarse May 30 '13 at 04:16

6 Answers6

30

TL;DR

This is definitely something new and it goes well beyond just being a way to avoid copying memory.

Long Answer: Why it's new and some perhaps non-obvious implications

Move semantics are just what the name implies--that is, a way to explicitly declare instructions for moving objects rather than copying. In addition to the obvious efficiency benefit, this also affords a programmer a standards-compliant way to have objects that are movable but not copyable. Objects that are movable and not copyable convey a very clear boundary of resource ownership via standard language semantics. This was possible in the past, but there was no standard/unified (or STL-compatible) way to do this.

This is a big deal because having a standard and unified semantic benefits both programmers and compilers. Programmers don't have to spend time potentially introducing bugs into a move routine that can reliably be generated by compilers (most cases); compilers can now make appropriate optimizations because the standard provides a way to inform the compiler when and where you're doing standard moves.

Move semantics is particularly interesting because it very well suits the RAII idiom, which is a long-standing a cornerstone of C++ best practice. RAII encompasses much more than just this example, but my point is that move semantics is now a standard way to concisely express (among other things) movable-but-not-copyable objects.

You don't always have to explicitly define this functionality in order to prevent copying. A compiler feature known as "copy elision" will eliminate quite a lot of unnecessary copies from functions that pass by value.

Criminally-Incomplete Crash Course on RAII (for the uninitiated)

I realize you didn't ask for a code example, but here's a really simple one that might benefit a future reader who might be less familiar with the topic or the relevance of Move Semantics to RAII practices. (If you already understand this, then skip the rest of this answer)

// non-copyable class that manages lifecycle of a resource
// note:  non-virtual destructor--probably not an appropriate candidate
//        for serving as a base class for objects handled polymorphically.
class res_t {
  using handle_t = /* whatever */;
  handle_t* handle;  // Pointer to owned resource
public:
  res_t( const res_t& src ) = delete;            // no copy constructor
  res_t& operator=( const res_t& src ) = delete; // no copy-assignment

  res_t( res_t&& src ) = default;                // Move constructor
  res_t& operator=( res_t&& src ) = default;     // Move-assignment

  res_t();                                       // Default constructor
  ~res_t();                                      // Destructor
};

Objects of this class will allocate/provision whatever resource is needed upon construction and then free/release it upon destruction. Since the resource pointed to by the data member can never accidentally be transferred to another object, the rightful owner of a resource is never in doubt. In addition to making your code less prone to abuse or errors (and easily compatible with STL containers), your intentions will be immediately recognized by any programmer familiar with this standard practice.

Iron Savior
  • 4,238
  • 3
  • 25
  • 30
  • I'm afraid that you're not being very clear. RAII was around long before move semantics, and because generally, RAII classes are not copyable or movable, I don't see any relationship between move semantics and RAII. – James Kanze May 29 '13 at 21:21
  • 3
    You're correct that RAII was around longer, but read what I said ("very well suits RAII"). The relationship is that movable-but-not-copyable objects *as a language feature* facilitates RAII by being semantically concise. What I said here doesn't begin to explain RAII, but I deemed such an explanation outside of the scope of this question. – Iron Savior May 29 '13 at 21:27
  • I added a some more detail clarifying my example application of move semantics to RAII. – Iron Savior May 29 '13 at 21:37
  • 10
    @JamesKanze: A good example of the move/RAII relationship would be `unique_ptr`. Without move semantics in C++03, `auto_ptr` was broken in containers (caused problems of ownership transfer eg. when resizing the container) and this was totally unfixable in a standard way. The thing is, RAII classes often used to be non-copyable but now those non-copyable classes can be movable without breaking the RAII idiom. – syam May 29 '13 at 21:38
  • I wouldn't say it is something technologically new though. It is just a part of the language now. But one can easily "do something like that" explicitly in C or C++98, for example. –  May 30 '13 at 13:12
  • 1
    True, but there wasn't a *standard* way to do it and it often didn't mesh easily with STL containers. You're right, the *semantic* is new, not the concept of ownership. – Iron Savior May 30 '13 at 13:13
  • @syam `auto_ptr` was illegal in containers, not broken. Which is fine with me; that's not one of the problems it was designed to solve (and there is almost never a need for smart pointers in containers anyway). The only real problem with `auto_ptr` was its complexity. Globally, it works better than `std::unique_ptr`. – James Kanze May 31 '13 at 09:08
  • @IronSavior But once you support move, it's no longer RAII. The whole point of RAII is that the resource has exactly the lifetime of the object which manages it (e.g. `std::scoped_lock`). Once you support move, you've broken RAII, since you can no longer be sure about the lifetime. (Having said that: it is a useful idiom to acquire the resource in a function, and return it, with "RAII" automatically taking effect after the return. It's not RAII, but it is fairly close. Maybe "extended RAII"?) – James Kanze May 31 '13 at 09:12
  • 1
    My understanding is that RAII doesn't require that an object be non-movable. When the object ownership is transferred, its lifetime is not less certain, it's merely managed by the new owner. – Iron Savior May 31 '13 at 14:22
  • @JamesKanze _"But once you support move, it's no longer RAII. The whole point of RAII is that the resource has exactly the lifetime of the object which manages it"_ ... erm, no. It's that something with a destructor owns the resource. Move semantics don't change that. Otherwise the existence of `boost::scoped_ptr::swap()` would mean it has broken RAII too. Also, there is no such type as `std::scoped_lock` – Jonathan Wakely May 31 '13 at 23:32
  • @JonathanWakely What I said is the original definition of RAII. `boost::scoped_ptr` is _not_ RAII, in any sense of the word, since you never know when the resource will be released (if ever). As for `scoped_lock`, I meant `lock_guard`, I think; I'm really more familiar with pre-C++11 in house implementations. – James Kanze Jun 01 '13 at 15:01
9

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-vectors 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<foo>, 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<T>(Args&&...), 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<foo>& ), you have std::vector<foo> get_vec(). This even works with multiple return values -- std::tuple< std::vector<A>, std::set<B>, 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.

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

There is absolutely something new going on here. Consider unique_ptr which can be moved, but not copied because it uniquely holds ownership of a resource. That ownership can then be transferred by moving it to a new unique_ptr if needed, but copying it would be impossible (as you would then have two references to the owned object).

While many uses of moving may have positive performance implications, the movable-but-not-copyable types are a much bigger functional improvement to the language.

In short, use the new techniques where it indicates the meaning of how your class should be used, or where (significant) performance concerns can be alleviated by movement rather than copy-and-destroy.

Mark B
  • 95,107
  • 10
  • 109
  • 188
2

No answer is complete without a reference to Thomas Becker's painstakingly exhaustive write up on rvalue references, perfect forwarding, reference collapsing and everything related to that.

see here: http://thbecker.net/articles/rvalue_references/section_01.html

ForeverLearning
  • 6,352
  • 3
  • 27
  • 33
0

I would say yes because a Move Constructor and Move Assignment operator are now compiler defined for objects that do not define/protect a destructor, copy constructor, or copy assignment.

This means that if you have the following code...

struct intContainer
{
    std::vector<int> v;
}

intContainer CreateContainer()
{
    intContainer c;
    c.v.push_back(3);
    return c;
}

The code above would be optimized simply by recompiling with a compiler that supports move semantics. Your container c will have compiler defined move-semantics and thus will call the manually defined move operations for std::vector without any changes to your code.

NtscCobalt
  • 1,639
  • 2
  • 15
  • 31
-8

Since move semantics only apply in the presence of rvalue references, which are declared by a new token, &&, it seems very clear that they are something new.

In principle, they are purely an optimizing techique, which means that: 1. you don't use them until the profiler says it is necessary, and 2. in theory, optimizing is the compiler's job, and move semantics aren't any more necessary than register.

Concerning 1, we may, in time, end up with an ubiquitous heuristic as to how to use them: after all, passing an argument by const reference, rather than by value, is also an optimization, but the ubiquitous convention is to pass class types by const reference, and all other types by value.

Concerning 2, compilers just aren't there yet. At least, the usual ones. The basic principles which could be used to make move semantics irrelevant are (well?) known, but to date, they tend to result in unacceptable compile times for real programs.

As a result: if you're writing a low level library, you'll probably want to consider move semantics from the start. Otherwise, they're just extra complication, and should be ignored, until the profiler says otherwise.

James Kanze
  • 150,581
  • 18
  • 184
  • 329
  • 11
    I am not entirely convinced that move semantics are just an optimization thing. They allow having non-copyable types in containers with value semantics, for instance. – Andy Prowl May 29 '13 at 21:42
  • 3
    1. move-only types? 2. the standard currently forbids most of those optimizations. – Marc Glisse May 29 '13 at 21:43
  • 1
    If they're useful for nothing else (which I don't believe) move semantics are useful for replacing the error-prone `auto_ptr` with the almost-idiotproof `unique_ptr`. Rvalue-references allow `unique_ptr` to Do The Right Thing automatically and safely, that's **not** just an optimization, it affects how easy it is to write safe code and have its correctness enforced by the compiler. – Jonathan Wakely May 30 '13 at 09:54
  • @AndyProwl Good point, maybe. In my experience, _most_ types which don't support copy are polymorphic, and have identity, neither of which will allow move. But I can imagine that there are exceptions. – James Kanze May 31 '13 at 09:14
  • @JonathanWakely What is error prone about `auto_ptr`? And `std::unique_ptr` doesn't work in one of the most frequent uses of `std::auto_ptr`, at least not with lots of complicated casting. Implementers don't like `std::auto_ptr`, because its specification changed so much, and is complicated to implement. But the `std::auto_ptr` in C++03 is superior to `std::unique_ptr` for users. – James Kanze May 31 '13 at 09:17
  • `auto_ptr`'s destructive copy semantics are surprising to many, and surprises lead to errors. I'm curious which most frequent use of `auto_ptr` you're referring to, and what complicated casts do you mean, calling `std::move`? IME users prefer `unique_ptr` because ownership transfer has to be explicit, so it's harder to do unintentionally – Jonathan Wakely May 31 '13 at 13:15
  • @JonathanWakely `auto_ptr`'s copy semantics are basically the same as `std::unique_ptr`'s move semantics. They don't cause any more problems than those of `std::unique_ptr`. And the one of the more frequent uses of `std::auto_ptr` is in factory functions, where we write `return std::auto_ptr( new Derived );`. Which doesn't compile without casts if you replace `std::auto_ptr` with `std::unique_ptr`. (`Derived`, of course, has a private destructor, so that it cannot be instantiated other than with `new`.) – James Kanze May 31 '13 at 13:32
  • 1
    @JamesKanze return std::unique_ptr(new Derived()); works just fine. What am I missing? – ForeverLearning May 31 '13 at 15:06
  • @Dilip It doesn't compile. `std::unique_ptr( D* )` requires that the destructor of `D` be accessible. For classes which should be allocated dynamically, it's common for the destructors to be private, so that they cannot accidentally be defined otherwise. (A bit of overkill, IMHO, but it's still a widespread idiom.) – James Kanze May 31 '13 at 18:33
  • _"std::unique_ptr( D* ) requires that the destructor of D be accessible."_ [[citation needed](http://en.wikipedia.org/wiki/Wikipedia:Citation_needed)] ... maybe you're thinking of `shared_ptr`?! – Jonathan Wakely May 31 '13 at 23:05
  • _"auto_ptr's copy semantics are basically the same as std::unique_ptr's move semantics."_ ... except that `unique_ptr p2 = p1;` doesn't compile, or do you consider `unique_ptr p2 = std::move(p1);` to be "the same"?! ... what's with the irrational and incorrect anti-unique_ptr agenda? You're usually a source of very sensible advice. – Jonathan Wakely May 31 '13 at 23:13
  • @JonathanWakely I just gave it a quick try with g++, and it didn't compile. I can't find the restriction either, but given that `std::unique_ptr` uses the same technique as `std::shared_ptr` for handling destruction, it must be there somewhere. (Although thinking about it: there's no need for it for either `std::shared_ptr` or `std::unique_ptr`. There's no need for the `template shared_ptr( U* ptr )` constructor, since the `D*` can be used directly to to initialize the `shared_ptr( T* )`. So maybe it's just a problem with current implementations. – James Kanze Jun 01 '13 at 14:53
  • @JonathanWakely It may be just an error in my implementation. I actually had the problem with `std::shared_ptr`, trying to convert existing software using a hacked `share_ptr` to use the standard. I the gave `std::unique_ptr` a quick try, with g++ 4.6.3. It didn't compile either, so I assumed the same problem. The standard does seem to have different constraints for the two, however, so it may be an artifact of an incomplete or incorrect implementation. (The support for C++11 is in g++ 4.6.3 _is_ rather experimental.) – James Kanze Jun 01 '13 at 14:58
  • It works fine with G++ 4.6 (see [this test](http://coliru.stacked-crooked.com/view?id=2a2bc5ed7b16e92099784c92945391a5-7d64639cfd0e54681ef583f7471117f3)), I don't know what you're testing. `shared_ptr` and `unique_ptr` do **not** use the same techniques, not even close. `shared_ptr` remembers the original type passed to the constructor and destroys it with that original type as the static type (which is why the `template shared_ptr(U*)` constructor is needed), `unique_ptr` performs a conversion in the constructor and only stores that converted type. – Jonathan Wakely Jun 02 '13 at 11:09