Saturday 15 March 2014

C++11 features - Move constructor

Problem in C++03: The cost of temporary objects
When a class has resources allocated inside itself (resources can be memory, file handler, database session, socket connection and so on), an explicit implementation of destructor is a must. For copy constructor and assignment operator they often appears in pair. Either both are required to be implemented  or both are required to be disabled by declaring them private with no implementation. Here we are talking about the first case, they are both required to be implemented.

In C++03 for those variables appear in the right-hand side of operator=, it normally means they are const reference and their value are not allowed to be modified. When doing user-defined objects with operator=, the only legal operations are designated to copy constructor and assignment operator. Here is the problem that the copy/assignment operations, called deep-copy, are normally very expensive because most of time they are implemented as bit-wised copy.
------------------------------------------------------------------------------------------------------------
class Bar;
class Foo {
pulic:
   Foo();
   ~Foo();
   Foo (Foo& f);
   Foo& operator=(const Foo&);
private:
   int m_x;
   Bar* m_Bar;
};

Foo a;
Foo b = a; // copy constructor      (p-1)
Foo c;  
c = a;        // assignment operator  (p-2)

Foo GetFoo() {
    Foo temp; // temporary objects
    .......
    return temp;  // more temporary objects create. (p-3)
}
------------------------------------------------------------------------------------------------------------
One of the problems is return-value type. (In C++03 we are taught not to return an object. However it is recommended to return a reference or pointer. But this will involve more or less memory management and risk of crashing the program by de-referencing of dangling reference/pointer.) In the above code (GetFoo()) an object of Foo is returned. Think about how this is achieved. One of methods to achieve it is to allocate heap memory to invoke copy constructor by passing the const reference of temp. Then temp disappears from stack as stack-unwinding when returning from function invoking. Return back to the caller of GetFoo, a temporary object is yet created again based on the object residing in heap memory and then delete the object in the heap. In the total a few temporary objects are created in deep-copy operation. Very expensive and inefficient. One of examples are the implementation of std::string and STL in C++.

Even though most modern compilers support return-type optimization, we still lose the control of how it is optimized. Therefore most of time we are recommended not  return an object.

The savior : C++11 rvalue reference and hence move constructor
rvalue reference - syntax notation: T&&
Comparing with const reference in C+03, rvalue reference is newly introduced in C++11. It is referring a temporary object which is allowed to be modified after initialization. This is will completely overturn the "move semantics" in C++03, which is sort of like:
    temp = a;
    a = b;
    b = temp;
Temporary objects are the  bridge. C++11 with the introduction of rvalue reference will do the swap, which is extremely efficient for allocated resources. Think about  a and b are pointing an array in the above example. A "temp" of array will be created for the move purpose in C++03. In the "swap semantics" the only thing need to do (move) is to exchange the pointer and the data can stay where they are (no temporary objects needed).

With this "swap semantics" concept, it is not only fast but also exception-safe comparing with C++03. What if exception happens when creating the temporary object (allocate memory and calling the constructor). The program could lose the control of the allocated resources. However C++11 the move constrcuctor uses "swap semantics" concept, therefore no temporary objects in the middle and it is exception-safe. To understand it better, read Item 22 and 23 of Herb Sutter's book "More Exceptional C++ - 40 New Engineering Puzzles, Programming Problems, and Solutions".
------------------------------------------------------------------------------------------------------------
Move constructor:
class Foo {
public:
    Foo(Foo&&);
    ......
};
------------------------------------------------------------------------------------------------------------
It has been confirmed that C++11 STL has move constructor implementation. And we don't have to worry about the return type of "vector<Foo>" or "vector<Foo>&" any more. As well we are no longer dependent on the compiler vendors to optimize the code.





No comments:

Post a Comment