Copy Constructor, Copy Assignment, Move Constructor and Move assignment

I'm having trouble trying to implement these special members.
I am reading from: http://www.cplusplus.com/doc/tutorial/classes2/#move

I have a code of what I have and my understanding of these special members.
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
template<typename T>
class A{

 public:
 A(){}
 ~A(){
 delete [] array;
 size = 1;
 }
 // Copy-constructor 
 A(const A &rhs){
  size = rhs.size;
  array = new T[rhs.size];
  for(int i = 0; i < rhs.size; i++){
   array[i] = rhs.array[i];
  }
 }
 //Copy Assignment
 A& operator=(const A &rhs){
  Chain copy = rhs;
  std::swap(*this, copy);
  return *this;
 }
 // Move Constructor
 // Moving also uses the value of an object to set the value to another object.
 // The source loses that content, which is taken over by the destination.
 A(A &&rhs){
  size = rhs.size;
  array = new T[rhs.size];
  for(int i = 0; i < rhs.size; i++){
   array[i] = rhs.array[i];
  }
  delete [] rhs.array;
  rhs.size = 1;
 }
 // Move-Assignment
 A& operator=(A &&rhs){
  delete [] array;
  array = new T[rhs.size];
  for(int i = 0; i < rhs.size; i++){
   array[i] = rhs.array[i];
  }
  delete [] rhs.array;
  rhs.size = 1;
  return *this;
 }

 private:
 size_t size = 1;
 T array = new T[size];
};


From what I understand, move constructor is similar to the copy constructor, after you transfer from the source object to the destination object, you delete the source object. I am sure that my code is incorrect, so I am asking if someone can clarify these special members to me.
Last edited on
> move constructor is similar to the copy constructor,
> after you transfer from the source object to the destination object, you delete the source object.

After inexpensively transferring the resources from the rvalue, you leave it in a safely-destructible state.

For instance:
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
64
65
66
67
68
#include <iostream>
#include <initializer_list>
#include <algorithm>

template < typename T > struct A
{
    A() = default ;
    A( std::size_t sz ) : sz(sz) {}

    // *** warning: assumes that the member sz is declared before ptr
    A( std::initializer_list<T> ilist ) : sz( ilist.size() )
    { std::copy( ilist.begin(), ilist.end(), ptr ) ;}

    // *** warning: assumes that the member sz is declared before ptr
    A( const A& that ) : sz(that.sz) { std::copy( that.ptr, that.ptr+sz, ptr ) ; }

    A( A&& that ) noexcept { swap(that) ; }

    // unifying assignment operator (copy and swap idiom)
    // used for both copy assignment and move assignment
    // https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Copy-and-swap
    A& operator= ( A that ) noexcept { swap(that) ; return *this ; }

    ~A() noexcept { delete[] ptr ; }


    void swap( A& that ) noexcept
    {
        using std::swap ;
        swap( sz, that.sz ) ;
        swap( ptr, that.ptr ) ;
    }

    std::size_t size() const noexcept{ return sz ; }

    void dump() const // for testing
    {
        std::cout << "[ " ;
        for( std::size_t i = 0 ; i < size() ; ++i ) std::cout << ptr[i] << ' ' ;
        std::cout << "]\n" ;
    }

    private:
        std::size_t sz = 0 ;
        T* ptr = new T[sz] {} ; // *** warning: slippery when wet
                                // *** code assumes that sz is declared before ptr
};

template < typename T > void swap( A<T>& a, A<T>& b ) noexcept { a.swap(b) ; }
template < typename T > void dump( const A<T>& a ) { a.dump() ; }

int main()
{
    A<int> a1 { 1, 2, 3, 4, 5 } ;

    A<int> a2 {a1} ;
    dump(a2) ; // [ 1 2 3 4 5 ]

    A<int> a3 { std::move(a1) } ;
    dump(a3) ; // [ 1 2 3 4 5 ]
    dump(a1) ; // [ ]

    a1 = a2 ;
    dump(a1) ; // [ 1 2 3 4 5 ]

    a1 = { 6, 7, 8 } ;
    dump(a1) ; // [ 6 7 8 ]
}

http://coliru.stacked-crooked.com/a/b91d34aa8cb1fe70
@JLBorges Thanks for the explanation for the move constructor.

I have refined my code a bit and I'm having problems deleting the source object.
When I try to perform a move using the constructor, it does not delete the source object.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct A{
  // Move constructor
  A(A &&rhs){
    std::swap(size, rhs.size_); 
    std::swap(array, rhs.array_);
  }
  // Move-assignment
  A& operator=(A &&rhs){

    std::swap(size, rhs.size_);
    std::swap(array, rhs.array_);
    return *this;
  }
};

int main(){

  A<int>a{10}; // [10]
  A<int>b{22); // [22]
  a = b; //  a = [10], b = [22]; b is incorrect
  A<int>c{a}; // c = [22], a = []; correct
  a = move(c); // a = [22], c = [22]; c is incorrect
  return 0;
}


I am not sure how I am able to use std::swap then delete the source object, since swap only moves the values of two objects.


Last edited on
> I am not sure how I am able to use std::swap then delete the source object,
> since swap only moves the values of two objects.

Repeat: you are not expected to delete the source object; you are expected to leave it in a safely destructible state.

For instance, in the code that you originally posted:
1
2
3
4
5
6
7
8
9
10
11
A(A &&rhs){
  size = rhs.size;
  array = new T[rhs.size];
  for(int i = 0; i < rhs.size; i++){
   array[i] = rhs.array[i];
  }
  // delete [] rhs.array;
  rhs.array = nullptr ; // make rhs safely destructible
  // rhs.size = 1;
  rhs.size = 0 ; // if we want to leave it in a consistent state
 }


Note: swap works because there are in-class member initialisers; by the time the body of the move constructor is entered into, we already have a default initialised object.,
Last edited on
I suggest not to use swap because that is not what copy/move is supposed to do.

In the move constructor/operator: Make a shallow copy (i.e. copy the pointer). Then set the object moved from to an empty state. Do not delete the moved data.

move was introduced to avoid unnecessary time consuming copy operation especially for returning a local container variable like vector.
I suggest not to use swap because that is not what copy/move is supposed to do.

In practice, that is pretty much what moving an object is supposed to do, since swapping can be implemented efficiently in the same way moving can.

Copy-and-swap-idiom's continuing relevancy in C++11:
http://stackoverflow.com/a/3279550
I realized my mistake, I failed to set the member variables size and array to the default initialized value. When I used std::swap it would swap the values, but would not perform the move function correctly if it was:
1
2
3
a = move(c);
std::swap(a.array, c.array); // a.array = c.array, c.array = a.array
std::swap(a.size, c.size); // a.size = c.size, c.size = a.size 


move(c) should cause c to be empty, therefore we need to swap a and c such that c would be empty after the swap.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  A(A &&rhs){
    // Set member variable to default
    size = 0;
    array = nullptr;
    std::swap(size, rhs.size_); 
    std::swap(array, rhs.array_);
  }
  // Move-assignment
  A& operator=(A &&rhs){
    // Set member variable to default
    size = 0;
    array= nullptr;
    std::swap(size, rhs.size_);
    std::swap(array, rhs.array_);
    return *this;
  }


Thanks for the help, and feel free to comment if I have the concept wrong because I recently started looking at C++11 and it takes some time to grasp the basics of C++11 since I'm still trying to master the basics of the older version of C++.
1
2
3
4
5
6
7
8
  A& operator=(A &&rhs){
    // Set member variable to default
    size = 0;
    array= nullptr;
    std::swap(size, rhs.size_);
    std::swap(array, rhs.array_);
    return *this;
  }


This is wrong. If array is pointing to anything you've just leaked it. Just doing the swaps leaves both objects in consistent states.
In practice, that is pretty much what moving an object is supposed to do, since swapping can be implemented efficiently in the same way moving can.
While this might be true, it also contradicts expectation and hence is error prone. Hard to detect errors like left and right values are really swapped.

Thus i would recommend swap only when swap is intended.
Last edited on
coder777 wrote:
it also contradicts expectation and hence is error prone
cire wrote:
Copy-and-swap-idiom's continuing relevancy in C++11:


I think you guys are talking about different things.

A move that is implemented in terms of swap is a valid move, and in fact GNU libstdc++ std::string's move actually swapped until std::string specification tweaks outlawed it:

using gcc 5.1 on http://melpon.org/wandbox/permlink/OERX8J45eh9iX0wb

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <utility>
#include <string>
 
int main()
{
    //  the moved-from object is NOT empty in gcc 4.x and 5.x
    std::string str1 = "Hello";
    std::string str2 = "Good-bye";
    std::cout << "Before move from str2, str2 = '" << str2 << "'\n";
    str1 = std::move(str2);
    std::cout << "After move from str2, str2 = '" << str2 << "'\n";
}
Before move from str2, str2 = 'Good-bye'
After move from str2, str2 = 'Hello'

I agree this violates the principle of the least surprise, given the general expectations of what move-assignment should do.

But a copy-and-swap assignment does not have that effect: it first moves out of the argument, and then modifies the parameter (by swapping and destroying), so the original argument is never touched by the swap.
Last edited on
@cire
Just doing the swaps leaves both objects in consistent states.


Without setting
1
2
size = 0;
array= nullptr;


a = move(c); would not work as intended. I have to copy c into a and empty c.

Following: https://msdn.microsoft.com/en-us/library/dd293665.aspx I have to release data pointer.
1
2
3
4
5
6
7
8
9
A& operator=(A &&rhs){
std::swap(size,rhs.size);
std::swap(array,rhs.array);
// Release data pointer.
rhs.size = 0;
rhs.array_ = nullptr;
// (c) would be empty
return *this;
}
Last edited on
Following: https://msdn.microsoft.com/en-us/library/dd293665.aspx I have to release data pointer.

No, you don't. Again, you're leaking memory here. The link you supply concerns a constructor. You aren't dealing with a constructor.

Setting a pointer to null does not "release" anything. If you must release the resource immediately in the removed-from object use the appropriate method (delete[] here,) prior to setting it to null although releasing it isn't strictly necessary since it will be released regardless when the original moved-from object is destroyed and its destructor runs.

The primary thing is to leave the objects in states consistent with the invariants for the type(s) involved so that the moved-from object can be safely destroyed (or assigned to) afterwards.
Topic archived. No new replies allowed.