C++11 Moving

Hey there,

I think I understand rvalue references, moving and perfect forwarding quite good now and I ask this question just to make sure I'm not wrong.
Are all of my assumptions correct?

Example 1: use compiler generated functions
- copy-constructor: copy a, b and c.
- move-constructor: also copies.
struct Data { int a, b, c; };

Example 2: use compiler generated functions
- copy-constructor: copy all 200 elements.
- move-constructor: also copies.
struct Data { int array[200]; };

Example 3: make move- and copy-constructor/assignment-operator
- copy-constructor: copy array elements.
- move-constructor: delete old array and point to new array, old object points to nullptr
struct Data { int* array; };

Example 4: make move- and copy-constructor/assignment-operator
- copy-constructor: copy array elements and a,b,c.
- move-constructor: delete old array and point to new array, old object points to nullptr, copy a,b,c
struct Data { int a, b, c; int* array; };

Please tell me if, where I'm wrong and how it should work.

My observation tells me that moving benefits performance when a class or struct manages ressources (allocates memory on the heap), otherwise it is as good as copying, is that correct?
Last edited on
Examples 3 and 4 are wrong:

Data does not have an array in those examples. So there are no elements to copy.

'array' is actually not an array... it is just a pointer. Therefore the only thing that the copy-constructor does is copy the pointer (aka a "shallow copy"). Move-constructor would be the same as the copy constructor.



There isn't really any pattern to memorize here. The compiler provided copy-constructor simply calls the copy constructor on all members, and the compiler provided move-constructor simply calls the move constructor on all members:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct Data
{
    int a, b, c;
    int* ptr;

    // compiler provided copy ctor does this:
    Data(const Data& rhs)
        : a(rhs.a)
        , b(rhs.b)
        , c(rhs.b)
        , ptr(rhs.ptr)
    {}
    
    // whereas the compiler provided move ctor does this:
    Data(Data&& rhs)
        : a( std::move(rhs.a) )
        , b( std::move(rhs.b) )
        , c( std::move(rhs.c) )
        , ptr( std::move(rhs.ptr) )
    {}
};


There's nothing more to it.

It just so happens that copy-ctors and move-ctors for basic types (including pointers) do a simple copy.


My observation tells me that moving benefits performance when a class or struct manages ressources (allocates memory on the heap), otherwise it is as good as copying, is that correct?


That is the idea. But it only works if your move-ctor actually does a move (or if one of your members has a move-ctor that does a move). Otherwise the move ctor is the same as the copy.

If you are writing a class that manages resources, you will need to manually overload both move and copy ctors and have them both do different things to get the expected results. (You'll also have to overload copy/move assignment operators and the destructor, too)
Last edited on
Examples 3 and 4 are wrong:

Data does not have an array in those examples. So there are no elements to copy.

'array' is actually not an array... it is just a pointer. Therefore the only thing that the copy-constructor does is copy the pointer (aka a "shallow copy"). Move-constructor would be the same as the copy constructor.

Well I named that variable array because I wanted to tell you that the pointer will allocate some memory in the constructor but I didn't want to write that code so i just called it array and though it would be enough for you to understand what I mean.

That's the Idea I had behind that name
1
2
3
4
5
6
class Data {
    int a, b, c;
    int* array;
public:
    Data() { array = new int[200]; } 
};


So to complete the class the copy and move-constructors should look like this to work properly, is that correct?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Data {
    int a, b, c;
    int* array;
public:
    Data() : a(), b(), c() { array = new int[200]; } 
    Data(const Data& d) :
        a(d.a), 
        b(d.b),
        c(d.c),
    {
        std::copy(d.array, d.array+200, array); 
    } 
    Data(Data&& d) : 
        a(std::move(d.a)), 
        b(std::move(d.b)), 
        c(std::move(d.c)),
    { 
        delete[] array;
        array = d.array;
        d.array = nullptr;
    } 
};


Do I miss something?

There isn't really any pattern to memorize here. The compiler provided copy-constructor simply calls the copy constructor on all members, and the compiler provided move-constructor simply calls the move constructor on all members:

Well, there is a pattern that could be memorized but it's also just a simple semantic when you understand it

It just so happens that copy-ctors and move-ctors for basic types (including pointers) do a simple copy.

Therefore the same thing goes for structs and classes that only have basic types as members

That is the idea. But it only works if your move-ctor actually does a move (or if one of your members has a move-ctor that does a move). Otherwise the move ctor is the same as the copy.

If you are writing a class that manages resources, you will need to manually overload both move and copy ctors and have them both do different things to get the expected results. (You'll also have to overload copy/move assignment operators and the destructor, too)

Okey good, so I got the right idea about this.

So at the beginning when i first heared about move semantics i thought "wow, magic!" but it comes down to the simple fact that the CopyConstructor is designed to copy data that is held by pointers and the move constructor to simply make each pointer point to the new location (and set the other to nullptr).

A few years ago I ran into exactly that problem (I just started programming and played around with pointers).
I though it would be a waste of time to copy the whole bunch of data but on the other hand i realized that the data is shared between 2 objects and therefore may be modified by any of them.
So the C++11 move semantics is designed to solve that problem (and maybe some more things?)
Last edited on
so i just called it array and though it would be enough for you to understand what I mean.


I understood that. But it doesn't change the fact that it's actually a pointer and therefore the copy constructor will not deep copy the data, and instead will merely shallow copy the pointer. That's what I was trying to draw attention to previously.

So to complete the class the copy and move-constructors should look like this to work properly, is that correct?

-code-

Do I miss something?


You almost have it. There are a few problems with that code -- and you are missing assignment operators and dtors (remember, if you need 1, then you need all 5). You'd need to allocate a buffer in the copy constructor.

And your move constructor isn't moving, but is destroying the buffer.

The class would look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
class Data
{
    int a, b, c;
    int* array;
public:
    Data() : a(), b(), c()
    {
        array = new int[200];       // this object allocates and owns this buffer
    } 
    
    Data(const Data& d) :
        a(d.a), 
        b(d.b),
        c(d.c)
    {
        array = new int[200];                   // this object allocates and owns this buffer
        std::copy(d.array, d.array+200, array); // <- copy data for that buffer from 'd's buffer
    } 
    Data(Data&& d) : 
        a(std::move(d.a)),      // the std::moves here aren't really necessary because they are basic types.
        b(std::move(d.b)),      //  but whatever.
        c(std::move(d.c))
    {
        array = d.array;        // we are not allocating an buffer, but are merely taking ownership of 'd's buffer
        d.array = nullptr;      // <- have 'd' release it so that it no longer owns it.
    } 
    
    // destructor
    ~Data()
    {
        // delete whatever array we own
        delete[] array;
    }
    
    // copy assignment
    Data& operator = (const Data& d)
    {
        // no need to reallocate, because this object already owns a buffer.  Simply copy
        //   the data over
        a = d.a;
        b = d.b;
        c = d.c;
        std::copy(d.array, d.array+200, array);
        
        return *this;
    }
    
    // move assignment
    Data& operator = (Data&& d)
    {
        // no need to reallocate -- or even to copy.  We just want to move ownership of d's buffer
        //   to this object.
        a = d.a;
        b = d.b;
        c = d.c;
        
        delete[] array;     // unallocate the buffer we currently own
        array = d.array;    // take ownership of d's buffer
        d.array = nullptr;  // have d release ownership of the buffer
        
        return *this;
    }
};



Again note:

- The compiler provided copy and move ctors/assignment operators will not do this, but instead will do a shallow copy of the pointer, resulting in memory leaks and crashes due to the same buffer being deleted multiple times (or not at all).

- With the introduction of move semantics, the "rule of 3" has become "rule of 5". There are 5 notable functions:
1) Copy ctor
2) Move ctor
3) Copy assignment operator
4) Move assignment operator
5) Destructor

If you class needs to implement any one of those 5, then it probably needs to implement all of them.


- Notice how much code there is just to implement a simple dynamically sized array. This is 60 lines of code and the class doesn't even do anything. Also notice how error prone it is and how easy it is to make a mistake or forget to implement one of these. This is exactly why manually managing memory is ill-advised, and why container classes like vector should be preferred.


By which I mean, this struct has pretty much identical functionaly to the above class:

1
2
3
4
5
6
7
8
struct Data
{
    int a, b, c;
    std::vector<int> array(200);
};

// Done.  Because 'array' is now an object, it has functioning copy/move ctors, so the compiler-provided
//   ctors for this struct will work exactly how you'd expect them to.  We don't need any additional code. 


By using a container instead of manually managing memory, we trimmed a 80 line error-prone class down to a 4-line impossible-to-screw-up struct.



but it comes down to the simple fact that the CopyConstructor is designed to copy data that is held by pointers


The way this is worded is weird.

If you are talking about the desired effect of the copy ctor, then yes, that is correct.

But if you are talking about the compiler-provided copy ctor, then no -- this is incorrect.

I want to emphasize... the compiler-provided copy constructor does NOT deep copy data that is held by pointers. It simply copies the pointer itself. It has no way to know what the pointer points to, and therefore it is impossible for it to copy the data. If you need it to deep copy the data, you have to write the copy ctor yourself.
Last edited on
As a general rule, the move constructor, the copy constructor and the destructor should be adorned with the noexcept specifier. Or else, we may not get the benefits of move semantics in many situations.

The compiler can use this information to enable certain optimizations on non-throwing functions as well as enable the noexcept operator, which can check at compile time if a particular expression is declared to throw any exceptions. For example, containers such as std::vector will move their elements if the elements' move constructor is noexcept, and copy otherwise (unless the copy constructor is not accessible, but a potentially throwing move constructor is, in which case the strong exception guarantee is waived).
http://en.cppreference.com/w/cpp/language/noexcept_spec

Also see: std::move_if_noexcept http://en.cppreference.com/w/cpp/utility/move_if_noexcept

Snippet illustrating the difference: http://coliru.stacked-crooked.com/a/e3279a2fd994d790
Disch wrote:
I understood that. But it doesn't change the fact that it's actually a pointer and therefore the copy constructor will not deep copy the data, and instead will merely shallow copy the pointer. That's what I was trying to draw attention to previously.
I'm sorry, i talked about the desired functionality for the copy constructor not the default generated version from the compiler

Disch wrote:
- With the introduction of move semantics, the "rule of 3" has become "rule of 5". There are 5 notable functions:
1) Copy ctor
2) Move ctor
3) Copy assignment operator
4) Move assignment operator
5) Destructor

If you class needs to implement any one of those 5, then it probably needs to implement all of them.
That makes sense

Disch wrote:
You almost have it. There are a few problems with that code -- and you are missing assignment operators and dtors (remember, if you need 1, then you need all 5). You'd need to allocate a buffer in the copy constructor.

And your move constructor isn't moving, but is destroying the buffer.
Oh, yeah, I almost forgot that.

Disch wrote:
- Notice how much code there is just to implement a simple dynamically sized array. This is 60 lines of code and the class doesn't even do anything. Also notice how error prone it is and how easy it is to make a mistake or forget to implement one of these. This is exactly why manually managing memory is ill-advised, and why container classes like vector should be preferred.
Yeah I know, I just want to know how the stuff works, that's all there is to it.

Disch wrote:
The way this is worded is weird.

If you are talking about the desired effect of the copy ctor, then yes, that is correct.

But if you are talking about the compiler-provided copy ctor, then no -- this is incorrect.

I want to emphasize... the compiler-provided copy constructor does NOT deep copy data that is held by pointers. It simply copies the pointer itself. It has no way to know what the pointer points to, and therefore it is impossible for it to copy the data. If you need it to deep copy the data, you have to write the copy ctor yourself.
Jup in all my posts I allways talked about the desired functionality, how would the compiler know that something is special in my class if I don't declare any of the methods that tell him "Hey, you have to be careful!"

JLBorges wrote:
As a general rule, the move constructor, the copy constructor and the destructor should be adorned with the noexcept specifier. Or else, we may not get the benefits of move semantics in many situations.
That's also some good piece of advice, thanks

Thank you for your help, I really appreciate it! :D
Last edited on
Topic archived. No new replies allowed.