Saturday, January 11, 2014

Chapter:06 Move Constructor

Fundamental
========

'move' constructor mechanism is one of the main core language feature introduced in C++11. As we all know that 'class' is the most key concept in C++ and 'move'  constructor is sort of directly associated with the the 'class' concept. Thats the reason I think its very important core language feature introduced. 'move' basically avoids the unnecessary making of copy of object,as many times it happens that there is no real usage of original copy of the object and copy was useless. So instead of the copy we can move the resource. By this way it avoid the scenario where multiple objects points to same resource. It does all those by simply moving the resource. Please be patient if someone has some confusion on these, I guess by the end of this chapter you would get what it all means.


I was watching one of Bjarne Stroustrup speech, he actually mentioned about this concept."When we need to put some object from our one hand to other, we simply move the object and its done. Even the 2-3 year kids know this however only stupid computer scientists would actually make a copy of that object and then move from one hand to other.”


The above lines says simple philosophy, do not complicate the things which can be done in simpler manner. And we can learn many things and apply in any field even from a kid. I have mentioned that this change directly impacts the core 'class' concept. Hence there are some fundamental changes which we need to know to really make use of this concept. Prior to C++11 standard, there were mainly 4 functions which managed the resource handling for a class. Now there are 2 additional functions which are introduced to support the 'move' mechanism.


1. Default Constructor.
2. Destructor.
3. Copy Constructor.
4. Assignment Operator.
5. Move Constructor.
6. Move Assignment Operator.


There are some rules which govern, when compilers would generate all 6 functions and its bit complicated. The simple rule is define all 6 whenever you have defined your own version for any of these. Otherwise default version of these functions, generated by compiler would be sufficient for us. Here I have not mention those rules as those are bit complicated,if you are interested please refer to books  which I have mentioned in this article. However important point over here is if you write these 6 functions with proper thinking and RAII concept, you do not have to worry about many things. Now you can pass any class object by value to any other function without worrying efficiency and safety. As usual we should pass any class by reference to any function while passing as argument.



In current program, I have written a simple class with all 6 functions. In addition to that I have put the appropriate message in each of these functions so that we can understand when these functions called.  The program is self explanatory and  reader should be able to get this. If anyone finds it difficult, I urge them to devote the time until they get this concept completely.



While understanding this new concept, keep one thing in mind that all previous concepts are still valid except for a couple of changes. Now if your program is returning any class object by value, the move assignment operator would execute. Sometime compiler would not be able to think as smart as we can. I mean if we know that there will not be any further usage of that particular objects and we want to make a copy of that, we  should call 'std::move()', which always calls move constructor if it is defined for that particular class. Otherwise copy constructor like old days concept would get  called under these situation.



//Code Begins

#include<iostream>

template<typename T>
void display(const T& val) {
    std::cout<<val<<std::endl;
}

class x {
public:
    x() {
        display("x::x(), default constructor");
    }

    ~x() {
        display("x::~x(), destructor");
    }

    x(const x& rhs) {
        display("x::x(const x&), copy constructor");
    }

    x& operator=(const x& rhs) {
        display("x::operator=(const x& rhs), assignment operator");
    }

    x(x&& rhs) {
        display("x::x(&& rhs), move constructor");
    }

    x& operator=(x&& rhs) {
        display("x::operator=(x&& rhs), move assignment operator");
    }
};


x function(void) {
    x o_c;
    return o_c;
}

void learn_move_constructor(void) {
    x o_a;
    x o_b(o_a);

    o_b = o_a;
    x o_d;
    o_d = function();
    x o_e = std::move(o_d);
}


int main(int argc, const char* argv[]) {
    learn_move_constructor();
    return 0;
}

//Code Ends



When I run the above program,I get the following output. If we watch them carefully or put the breakpoint on all these 6 functions under gdb and study the stack trace, we can understand them completely.



//Console Output Begins

$ ./test
x::x(), default constructor
x::x(const x&), copy constructor
x::operator=(const x& rhs), assignment operator
x::x(), default constructor
x::x(), default constructor
x::operator=(x&& rhs), move assignment operator
x::~x(), destructor
x::x(&& rhs), move constructor
x::~x(), destructor
x::~x(), destructor
x::~x(), destructor
x::~x(), destructor

//Console Output Ends




Implementation
==========

The previous section was sort of net practice before the actual match. The following examples demonstrate the real implementation and key concepts of
'move' semantics. We have discussed in the previous example that 'move' actually move the resource pointer from left side object of assignment to  right side, as left side object would no longer be used once we go out of the scope of that particular function. However we know that whenever any objects go out of scope, its destructor gets called. So at this point the left side object state should be such that it destructor could execute successfully.


'move' constructor basically achieves this by doing pointer like assignment for moving resource from one object to other. Once its done it also resets the left side object pointer to the default values like 'nullptr' or 0 whereever  appropriate. By this destructor gets called for the left side object when going out of scope correctly as objects invariants are in valid state. All actual resource would be transferred to right side object in the assignment. This is the key concept in 'move'.


We need to pass the argument by 'reference' not by 'const reference' as we do in copy constructor and assignment operator. This is necessary as inside 'move' constructors we need to change the passing argument objects state as well. This can not be achieved if we pass the argument by 'const reference'. We also need to use double ampersand to distinguish from their copy constructors and assignment operators. This double ampersand also known as "rvalue" which actually 'move'  constructor would use.


Hope above explanation makes sense to reader. If some one has still some confusion, we would try to understand this by debugging this program and would verify when really 'move' constructors gets called.




//Code Begins

#include<iostream>
#include<cstring>
#include<algorithm>

template<typename T>
void display(const T& val) {
    std::cout<<val<<std::endl;
}

class x {
public:
    x(const char* n):length(20), ptr(nullptr){
        ptr = new char[length];
        std::fill(ptr, ptr+length,'\0');
        strncpy(ptr,n,length);
    }

    ~x() {
        delete [] ptr;
    }

    x(const x& rhs) {
        length = rhs.length;
        ptr = new char[length];
        std::fill(ptr,ptr+length,'\0');
        strncpy(ptr,rhs.ptr,length);
    }

    x& operator=(const x& rhs) {
        if(this != &rhs) {
            delete [] ptr;
            length = rhs.length;
            ptr = new char[length];
            std::fill(ptr,ptr+length,'\0');
            strncpy(ptr,rhs.ptr,length);
        }
        return *this;
    }

    x(x&& rhs) {
        length = rhs.length;
        ptr = rhs.ptr;
        rhs.length = 0;
        rhs.ptr = nullptr;
    }

    x& operator=(x&& rhs) {
        delete [] ptr;
        length = rhs.length;
        ptr = rhs.ptr;
        rhs.length = 0;
        rhs.ptr = nullptr;
        return *this;
    }

    void display_element(void) {
        if(ptr != nullptr) {
            display(ptr);
        }
    }

private:
    std::size_t length;
    char* ptr;
};



x function(void) {
    const char* na="Kumar";
    x o_c(na);
    return o_c;
}

void learn_move_constructor(void) {
    const char* nb="Mantosh";
    x o_a(nb);
    x o_b(o_a);

    o_b = o_a;

    const char* nc="Sharma";
    x o_d(nc);
    o_d = function();

    x o_e = std::move(o_d);

    o_a.display_element();
    o_b.display_element();
    o_d.display_element();
    o_e.display_element();

}

int main(int argc, const char* argv[]) {
    learn_move_constructor();
    return 0;
}
//Code Ends




When I run the above program, I get three messages on output console. From these novice readers will not be able to make much about internal   concept around this. Hence below I have explained this concept using the gdb debugger with appropriate explanation whereever applicable. Here we can put the break points on 'move constructor' and 'move assignment operator' and 'destructor' as  these are important functions at this point. Remaining 3 functions  are well known to us as its not something which is introduced in C++11.



//Debugger Output Begins

//Put the break point on the move constructor
(gdb) b x::x(x&&)
Breakpoint 2 at 0x400e92: file test_2.cpp, line 41.

//Put the break point on the move assignment operator
(gdb) break x::operator=(x&&)
Breakpoint 3 at 0x400eda: file test_2.cpp, line 48.

//Put the break point on the destructor as we want to see how
//destructor behaves for the objects which has been moved.
(gdb) break x::~x()
Breakpoint 4 at 0x400d14: file test_2.cpp, line 19.

//Once break point set, continue/run the program.
(gdb) continue
Continuing.

Breakpoint 3, x::operator=(x&&) (this=0x7fffffffe0b0, rhs=...)
48     delete [] ptr;

//At this point(LineNumber:84), the object(o_c) from the
//function() would be assigned to o_d object in the function
//learn_move_constructor(). At this point both the objects
//contains some resources, so we need to delete this object
//resources before assigning from other. This is same as we
//do in the assignment operator.
(gdb) backtrace
#0  x::operator=(x&&) (this=0x7fffffffe0b0, rhs=...)
#1  0x0000000000400b22 in learn_move_constructor ()
#2  0x0000000000400c18 in main (argc=1, argv=0x7fffffffe1f8)

//At present, the following are the contents of both the
//objects.Once we are done the resources of 'rhs' should
//assigned to 'this' object and 'rhs' should be in empty state
//as when we would return from the function f() scope, the
//destructor for 'rhs' object would gets called.
(gdb) p *this
$1 = {
  length = 20,
  ptr = 0x603050 "Sharma"
}
(gdb) p rhs
$2 = (x &) @0x7fffffffe0c0: {
  length = 20,
  ptr = 0x603070 "Kumar"
}

//Do 'next' command till you reach to end of the
//x::operator=(x&&).
(gdb) next
49     length = rhs.length;
(gdb) next
50     ptr = rhs.ptr;
(gdb)
51     rhs.length = 0;
(gdb)
52     rhs.ptr = nullptr;
(gdb)
53     return *this;

//At this point we can see that now 'rhs' resources are moved to
//'this' object. 'rhs(0x7fffffffe0c0)' is in the empty state.
(gdb) p *this
$3 = {
  length = 20,
  ptr = 0x603070 "Kumar"
}
(gdb) p rhs
$4 = (x &) @0x7fffffffe0c0: {
  length = 0,
  ptr = 0x0
}

//We again continue from here and the next time program breaks when
//the destructor gets called for the 'rhs(0x7fffffffe0c0)' object.
//This can be verified from the address and their values. Now
//when destructor would called for this 'empty' object, it
//would not do anything. I mean 'delete[]' would simply ignore
//for 'nullptr'. This is what we wanted to achieve in 'move'.
(gdb) continue
Continuing.

Breakpoint 4, x::~x (this=0x7fffffffe0c0, __in_chrg= ...>)
19      delete [] ptr;

(gdb) backtrace
#0  x::~x (this=0x7fffffffe0c0, __in_chrg=<optimized out>)
#1  0x0000000000400b2e in learn_move_constructor ()
#2  0x0000000000400c18 in main (argc=1, argv=0x7fffffffe1f8)
(gdb) p *this
$6 = {
  length = 0,
  ptr = 0x0
}
(gdb) p this
$7 = (x * const) 0x7fffffffe0c0

//After this, again we do 'next' under gdb to execute next line.
//In the program, I have called std::move() to explain how we
//can make use of 'move' semantic explicitly when compiler
//may not recognize that stuff. The same way again it would
//move the resources from 'rhs' object and make its state 'empty'
//In this context, 'this' does not have any resources with him so it
//does not need to release any prior resources. This is the same as
//copy constructor.
(gdb) next
20     }
(gdb)
learn_move_constructor () at test_2.cpp:86
86     x o_e = std::move(o_d);
(gdb) p o_d
$8 = {
  length = 20,
  ptr = 0x603070 "Kumar"
}

//At this point, move constructor gets called and which
//would do the same thing, assign the resources and make its
//own state 'empty'.
(gdb) continue
Continuing.

Breakpoint 2, x::x(x&&) (this=0x7fffffffe0d0, rhs=...)
41     length = rhs.length;

(gdb) backtrace
#0  x::x(x&&) (this=0x7fffffffe0d0, rhs=...)
#1  0x0000000000400b4c in learn_move_constructor ()
#2  0x0000000000400c18 in main (argc=1, argv=0x7fffffffe1f8)

(gdb) print this
$9 = (x * const) 0x7fffffffe0d0

(gdb) print rhs
$11 = (x &) @0x7fffffffe0b0: {
  length = 20,
  ptr = 0x603070 "Kumar"
}

//These are the move constructor linw, which is executing here.
(gdb) next
42     ptr = rhs.ptr;
(gdb)
43     rhs.length = 0;
(gdb)
44     rhs.ptr = nullptr;
(gdb)
45    }

//At this point we can see that 'rhs' resource has been moved to
//'this'. At this point also there would not be any problem with
//destructor call as 'rhs' state are still in destructible state.
(gdb) print rhs
$12 = (x &) @0x7fffffffe0b0: {
  length = 0,
  ptr = 0x0
}
(gdb) print *this
$13 = {
  length = 20,
  ptr = 0x603070 "Kumar"
}

//Debugger Output Ends


No comments:

Post a Comment