Tuesday 18 March 2014

The order of object initialization/destruction

1. Objections initialization of class/struct and its member
The order of initialization : the member variables are initialized before this class's constructor is called.
- Allocate the memory for this object
- The initialization order of member variables are the same as the order in which they are declared in the class.
- Call the constructor and the life of this object begins

The order of destruction : the reverse order of initialization
- The destructor of the class is called first
- The destructor of it memebers are called in the reverse order in which they are declared int the class.
- de-allocate the memory

struct A{
    A() {std::cout << "A()" << std::endl;}
    ~A() {std::cout << "~A()" << std::endl;}
};

struct B{
    B() {std::cout << "B()" << std::endl;}
    ~B() {std::cout << "~B()" << std::endl;}
};

struct C{
    C() {std::cout << "C()" << std::endl;}
    ~C() {std::cout << "~C()" << std::endl;}
};
struct Base{
    Base() {std::cout << "Base()" << std::endl;}
    ~Base() {std::cout << "~Base()" << std::endl;}

    A a;
    B b;
    C c;
};

If we do
int main(int argc, char* argv[])
{
    {
        Base b;
    }
    return 0;
}
Output:
        A()
        B()
        C()
        Base()
        ~Base()
        ~C()
        ~B()
        ~A()
The order is quite clear
- the member variables firstly initialized in the order they are declared
- Then class itself
- Destroy in the reverse order of initialization

Memory map of Base
    ---------------------------------------------------------------------------
    |                                                                                                            
    |     virtual table pointer: if Base has virtual function                                  
    |                                                                                                            
    ---------------------------------------------------------------------------
    |                                                                                                            
    |                                                                                                            
    |                                                                                                            
    |     Memory for A, call A's constructor to initialize this memory              
    |                                                                                                            
    |                                                                                                            
    |                                                                                                            
    |                                                                                                            
    ---------------------------------------------------------------------------
    |                                                                                                              
    |                                                                                                            
    |                                                                                                            
    |    Memory for B, call B's constructor to initialize this memory                
    |                                                                                                              
    |                                                                                                              
    |                                                                                                          
    |                                                                                                            
    ---------------------------------------------------------------------------
    |                                                                                                              
    |                                                                                                            
    |   Memory for C, call C's constructor to initialize this memory                
    |                                                                                                              
    |                                                                                                              
    |                                                                                                            
    ---------------------------------------------------------------------------

2. The initialization/destruction order of inherited classes
The initialization order of the inherited class
- Allocate the memory for itself
- Initialize the base class member variables
- Initialize the base class
[
    // if multi-inheritance
    - Initialize the 2nd base class member variables
    - Initialize the 2nd base class
    ........
]
- Initialize the sub-class's member variables
- Initialize the sub-class itself

The destruction order of inherited class: the reverse order of the initialization
struct D{
    D() {std::cout << "D()" << std::endl;}
    ~D() {std::cout << "~D()" << std::endl;}
};

struct E{
    E() {std::cout << "E()" << std::endl;}
    ~E() {std::cout << "~E()" << std::endl;}
};
struct Foo : public Base{
    Foo() {std::cout << "Foo()" << std::endl;}
    ~Foo() {std::cout << "~Foo()" << std::endl;}

    D d;
    E e;
};

If we do
int main(int argc, char* argv[])
{
    {
        Foo f;
    }
    return 0;
}
Output:
        A()
        B()
        C()
        Base()
        D()
        E()
        Foo()
        ~Foo()
        ~E()
        ~D()
        ~Base()
        ~C()
        ~B()
        ~A()

Memory map of Foo
    ---------------------------------------------------------------------------
    |                                                                                                            
    |     virtual table pointer: if Foo has virtual functions                                  
    |                                                                                                            
    ---------------------------------------------------------------------------
    |--------------------------------------------------------------------------
    | | Memory for Base
    | |-----------------------------------------------------------------------                                            
    | |                                                                                                            
    | |                                                                                                            
    | |    Memory for A, call A's constructor to initialize this memory              
    | |                                                                                                                                              
    | |                                                                                                            
    | |                                                                                                            
    | |------------------------------------------------------------------------
    | |                                                                                                            
    | |                                                                                                            
    | |                                                                                                            
    | |   Memory for B, call B's constructor to initialize this memory                
    | |                                                                                                            
    | |                                                                                                            
    | |                                                                                                          
    | |                                                                                                            
    | |-------------------------------------------------------------------------
    | |                                                                                                              
    | |                                                                                                            
    | |  Memory for C, call C's constructor to initialize this memory                
    | |                                                                                                            
    | |                                                                                                            
    | |                                                                                                            
    |--------------------------------------------------------------------------
    | |
    | |  Memory of other base classes if Foo inherits from multi-classes
    | |
    ---------------------------------------------------------------------------
    |
    |
    |
    |   Memory for D, call D's constructor to initialize this memory
    |
    |
    ---------------------------------------------------------------------------
    |
    |
    |
    |   Memory for E, call E's constructor to initialize this memory
    |
    |
    ---------------------------------------------------------------------------

3. The initialization/destruction order in array
The initialization order is to create objects from low index to high index. The destruction is the reverse order of the initialization order

struct F{
    F():m_Count(s_Count) {
        std::cout << "F(" << m_Count << ")" << std::endl;
        ++s_Count;
    }

    F(const F& f) : m_Count(s_Count) {
        std::cout << "copy to F(" << m_Count << ")" << std::endl;
        ++s_Count;      
    }

    ~F() {std::cout << "~F(" << m_Count << ")" << std::endl;}

    static int s_Count;
    int m_Count;
};

int F::s_Count = 0;

If we do
int main(int argc, char* argv[])
{
    {
        F fArr[5];
    }
    return 0;
}
Output:
        F(0)
        F(1)
        F(2)
        F(3)
        F(4)
        ~F(4)
        ~F(3)
        ~F(2)
        ~F(1)
        ~F(0)

4. The initialization/destruction order in std::vector
The objects are initialized from low index to high index order. And the destruction order is the same order as the initialization

If we do,
int main(int argc, char* argv[])
{
    {
        std::vector<F> fVec(5, F());
    }
    return 0;
}
Output:
        F(0)
        copy to F(1)
        copy to F(2)
        copy to F(3)
        copy to F(4)
        ~F(0)
        ~F(1)
        ~F(2)
        ~F(3)
        ~F(4)
Internally the copy constructor (the first object as the argument) is called to create new objects for the rest of the vector.

If we do,
int main(int argc, char* argv[])
{
    {
        std::vector<F> fVec(5);
    }
    return 0;
}
Output:
        F(0)
        copy to F(1)
        F(2)
        copy to F(3)
        F(4)
        copy to F(5)
        F(6)
        copy to F(7)
        F(8)
        copy to F(9)
        ~F(1)
        ~F(3)
        ~F(5)
        ~F(7)
        ~F(9)
This is the output of Miscrosoft 2010 Visual C++ Express. See how inefficient it is. 5 temporary objects are created and destroyed when creating a 5-objects vector. CRAZY!

5. Use initialization list for constructor
Eliminate unnecessary temporary object created and deleted for initializing member variables.
struct G{
    G() : m_X(0) {
        std::cout << "G()" << std::endl;
    }
    G(int i) : m_X(i) {
        std::cout << "G(int)" << std::endl;
    }
    ~G() {
        std::cout << "~G()" << std::endl;
    }

    int m_X;
};
struct Bar{
    Bar(int i) {
        std::cout << "Bar(int)" << std::endl;
        m_G = i;
    }
    ~Bar() {std::cout << "~Bar()" << std::endl;}

    G m_G;
};

If we do,
int main(int argc, char* argv[])
{
    {
        Bar b;
    }
    return 0;
}
Output:
        G()
        Bar(int)
        G(int)
        ~G()
        ~Bar()
        ~G()
The default constructor G() is called to initialize m_G before getting into constructor Bar(int). When hitting "m_G = i", constructor G(int) was called to re-initialize m_G. Then old value of m_G is to be destructed, which is the temporary object created and destructed. This should be avoided.

If we modify Bar as
struct Bar{
    Bar(int i) : m_G(i) {
        std::cout << "Bar(int)" << std::endl;
    }
    ~Bar() {std::cout << "~Bar()" << std::endl;}

    G m_G;
};
Output:
        G(int)
        Bar(int)
        ~Bar()
        ~G()
"m_G" is to be initialized directly by passing "i" as the argument in the initialization list. Then no temporary object is created and destructed.

6. Initialize the objects in the initialization list in the same order in which they are declared
This is especially true when some member variables are taking other member variables as the arguments. The defendants have to be declared after the member variables they are depending.

struct B{
    B(int i);
};
struct C{
    C(int i);
};
struct A{
    A(const B&, const C&);
}

struct Foo{
    Foo(int i, int j) : m_B(i), m_C(j), m_A(m_B, m_C) {}

    B m_B;
    C m_C;
    A m_A;
};
"m_A" is dependent on "m_B" and "m_C". Then "m_B" and "m_C" have to be initialized before "m_A". It will require "m_B" and "m_C" has to be declared before "m_A". Otherwise in the initialization list when hitting "m_A(m_B, m_C)", "m_B" and "m_C" are not initialized yet. Then will cause crash.


No comments:

Post a Comment