Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

I recently have been reading about move constructors in C++ (see e.g. here) and I am trying to understand how they work and when I should use them.

As far as I understand, a move constructor is used to alleviate the performance problems caused by copying large objects. The wikipedia page says: "A chronic performance problem with C++03 is the costly and unnecessary deep copies that can happen implicitly when objects are passed by value."

I normally address such situations

  • by passing the objects by reference, or
  • by using smart pointers (e.g. boost::shared_ptr) to pass around the object (the smart pointers get copied instead of the object).

What are the situations in which the above two techniques are not sufficient and using a move constructor is more convenient?

share|improve this question
1  
Besides the fact that move semantics can achieve much more (as said in the answers), you shouldn't ask what are the situations where passing by reference or by smart pointer are not sufficient, but if those techniques are really the best and cleanest way to do so (god beware a shared_ptr just for the sake of fast copying) and if move semantics can achieve the same with nearly no coding-, semantics- and cleanliness-penalty. –  Christian Rau Jun 1 '12 at 9:27
add comment

4 Answers 4

up vote 10 down vote accepted

Move semantics introduce a whole dimension to C++ - it isn't just there to let you return values cheaply.

For example, without move-semantics std::unique_ptr is useless - look at std::auto_ptr. Moving a resource is vastly different from copying it. It allows transfer of ownership of a unique item.

For example, let's not look at std::unique_ptr, since it is fairly well discussed. Let's look at, say, a Vertex Buffer Object in OpenGL. A vertex buffer represents memory on the GPU - it needs to be allocated and deallocated using special functions, possibly having tight constraints on how long it can live. It is also important that only one owner use it.

class vertex_buffer_object
{
    vertex_buffer_object(size_t size)
    {
        this->vbo_handle = create_buffer(..., size);
    }

    ~vertex_buffer_object()
    {
        release_buffer(vbo_handle);
    }
};

void create_and_use()
{
    vertex_buffer_object vbo = vertex_buffer_object(SIZE);

    do_init(vbo); //send reference, do not transfer ownership

    renderer.add(std::move(vbo)); //transfer ownership to renderer
}

Now, this could be done with a std::shared_ptr - but this resource is not to be shared. This makes it confusing to use a shared_ptr. You could use std::unique_ptr, but that still requires move semantics.

Obviously, I haven't implemented a move constructor, but you get the idea.

The relevant thing here is that some resources aren't copyable. You can pass around pointers instead of moving, but unless you use unique_ptr, there is the issue of ownership. It is worthwhile to be as clear as possible as to what the intent of the code is, so a move-constructor is probably the best approach.

share|improve this answer
    
Thanks for the answer. What would happen if one used a shared pointer here? –  Giorgio May 31 '12 at 19:02
    
I try to answer myself: using a shared pointer would not allow to control the lifetime of the object, while it is a requirement that the object can only live for a certain amount of time. –  Giorgio May 31 '12 at 19:25
2  
@Giorgio You could use a shared pointer, but it would be semantically wrong. It isn't possible to share a buffer. Also, that would essentially make you pass a pointer to a pointer (since the vbo is basically a unique pointer to GPU memory). Someone viewing your code later might wonder 'Why is there a shared pointer here? Is it a shared resource? That might be a bug!'. It's better to be as clear as possible as to what the original intent was. –  Max May 31 '12 at 19:25
    
@Giorgio Yes, that is also part of the requirement. When the 'renderer' in this case wants to deallocate some resource (possibly not enough memory for new objects on the GPU), there must not be any other handle to the memory. Using a shared_ptr that passes out of scope would work if you don't keep it around anywhere else, but why not make it completely obvious when you can? –  Max May 31 '12 at 19:28
    
@Giorgio See my edit for another try at clarifying. –  Max May 31 '12 at 19:32
show 3 more comments

Return values are where I'd most often like to pass by value instead of some kind of reference. Being able to quickly return an object 'on the stack' without a massive performance penalty would be nice. On the other hand, it's not particularly difficult to get around this (shared pointers are just so easy to use...), so I'm not sure it's really worth doing extra work on my objects just to be able to do this.

share|improve this answer
    
I also normally use smart pointers to wrap objects that are returned from a function / method. –  Giorgio May 31 '12 at 18:39
    
@Giorgio: That's definitely both obfuscating and slow. –  DeadMG May 31 '12 at 21:40
add comment

Move semantics aren't necessarily all that big of an improvement when you're returning a value -- and when/if you use a shared_ptr (or something similar) you're probably prematurely pessimizing. In reality, nearly all reasonably modern compilers do what's called Return Value Optimization (RVO) and Named Return Value Optimization (NRVO). This means that when you're returning a value, instead of actually copying the value at all, they simply pass a hidden pointer/reference to where the value's going to be assigned after the return, and the function uses that to create the value where it's going to end up. The C++ standard includes special provisions to allow this, so even if (for example) your copy constructor has visible side effects, it's not required to use the copy constructor to return the value. For example:

#include <vector>
#include <numeric>
#include <iostream>
#include <stdlib.h>
#include <algorithm>
#include <iterator>

class X {
    std::vector<int> a;
public:
    X() {
        std::generate_n(std::back_inserter(a), 32767, ::rand);
    }

    X(X const &x) {
        a = x.a;
        std::cout << "Copy ctor invoked\n";
    }

    int sum() { return std::accumulate(a.begin(), a.end(), 0); }
};

X func() {
    return X();
}

int main() {
    X x = func();

    std::cout << "sum = " << x.sum();
    return 0;
};

The basic idea here is fairly simple: create a class with enough content we'd rather avoid copying it, if possible (the std::vector we fill with 32767 random ints). We have an explicit copy ctor that'll show us when/if it gets copied. We also have a little more code to do something with the random values in the object, so the optimizer won't (at least easily) eliminate everything about the class just because it does nothing.

We then have some code to return one of these objects from a function, and then use the summing to ensure the object is really created, not just ignored completely. When we run it, at least with most recent/modern compilers, we find that the copy constructor we wrote never runs at all -- and yes, I'm pretty sure that even a fast copy with a shared_ptr is still slower than doing no copying at all.

Moving allows you to do a fair number of things you simply couldn't do (directly) without them. Consider the "merge" part of an external merge sort -- you have, say, 8 files you're going to merge together. Ideally you'd like to put all 8 of those files into a vector -- but since vector (as of C++03) needs to be able to copy elements, and ifstreams can't be copied, you're stuck with some unique_ptr/shared_ptr, or something on that order to be able to put them in a vector. Note that even if (for example) we reserve space in the vector so we're sure our ifstreams will never really be copied, the compiler won't know that, so the code won't compile even though we know the copy constructor will never be used anyway.

Even though it still can't be copied, in C++11 an ifstream can be moved. In this case, the objects probably won't ever be moved, but the fact that they could be if necessary keeps the compiler happy, so we can put our ifstream objects in a vector directly, without any smart pointer hacks.

A vector that does expand is a pretty decent example of a time that move semantics really can be/are useful though. In this case, RVO/NRVO won't help, because we're not dealing with the return value from a function (or anything very similar). We have one vector holding some objects, and we want to move those objects into a new, larger chunk of memory.

In C++03, that was done by creating copies of the objects in the new memory, then destroying the old objects in the old memory. Making all those copies just to throw away the old ones, however, was quite a waste of time. In C++11, you can expect them to be moved instead. This typically lets us, in essence, do a shallow copy instead of a (generally much slower) deep copy. In other words, with a string or vector (for only a couple of examples) we just copy the pointer(s) in the objects, instead of making copies of all the data those pointers refer to.

share|improve this answer
    
Thanks for the very detailed explanation. If I understand correctly, all the situations in which moving comes into play could be handled by normal pointers, but it would be unsafe (complex and error prone) to program all the pointer juggling each time. So, instead, there is some unique_ptr (or similar mechanism) under the hood and the move semantics ensures that at the end of the day there is only some pointer copying and no object copying. –  Giorgio Jun 1 '12 at 5:15
    
@Giorgio: Yes, that's pretty much correct. The language doesn't really add move semantics; it adds rvalue references. An rvalue reference (obviously enough) can bind to an rvalue, in which case you know it's safe to "steal" its internal representation of the data and just copy its pointers instead of doing a deep copy. –  Jerry Coffin Jun 1 '12 at 5:17
add comment

Consider:

vector<string> v;

When adding strings to v, it will expand as needed, and on each reallocation the strings will have to be copied. With move constructors, this is basically a non-issue.

Of course, you could also do something like:

vector<unique_ptr<string>> v;

But that will work well only because unique_ptr implements move constructor.

Using shared_ptr makes sense only in (rare) situations when you actually have shared ownership.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.