[C + +] calling sequence of constructor and destructor

Posted by stew on Fri, 25 Feb 2022 05:39:07 +0100

1, Global, static and local variables

Global variables call the constructor at the beginning of the program and the destructor at the end of the program.
Static variables call the constructor when the function is called for the first time and the destructor at the end of the program. They are called only once.
The constructor is called when the code segment where the local variable is located is executed, and the destructor is called when it leaves its scope (the area enclosed in braces). Can be called any number of times.

Let's explain in more detail through the code:

#include <iostream>

using namespace std;

// The following three classes are defined to output when calling the constructor and destructor
class A
{
public:
    A()
    {
        cout << "+ Constructor of A\n"; // Constructor
    }
    ~A()
    {
        cout << "- Destructor of A\n"; // Destructor
    }
};

class B
{
public:
    B()
    {
        cout << "+ Constructor of B\n";
    }
    ~B()
    {
        cout << "- Destructor of B\n";
    }
};

class C
{
public:
    C()
    {
        cout << "+ Constructor of C\n";
    }
    ~C()
    {
        cout << "- Destructor of C\n";
    }
};

// Test below

A global; // global variable

void function() // Function called 3 times
{
    cout << "Beginning of function.\n"; // Function start
    static B static_object; // Static variable
    { // The scope of the local variable starts
        C local_object; // local variable
    } // The scope of the local variable ends
    cout << "End of function.\n"; // End of function
}

int main()
{
    cout << "Call function for 4 times ---------------\n";
    // Call function 3 times
    for(int i = 1; i < 4; ++i) function();
    cout << "Main function exiting -------------------\n";
    // End of main function
    return 0;
}
// Program end

The results are as follows:

+ Constructor of A
Call function for 4 times ---------------
Beginning of function.
+ Constructor of B
+ Constructor of C
- Destructor of C
End of function.
Beginning of function.
+ Constructor of C
- Destructor of C
End of function.
Beginning of function.
+ Constructor of C
- Destructor of C
End of function.
Main function exiting -------------------
- Destructor of B
- Destructor of A

First, for the global variable A global, it is called at the beginning of the program and destructed at the end of the program.
Secondly, for the static variable static B static_object, which is constructed when function is called for the first time and destructed at the end of the program.
Then, for the local variable C local_object, which is called every time the function reaches its step, is destructed at the end of its curly bracket, not at the end of the function.

We can sum up a rule: every time we call the destructor, we always destruct the recently constructed object that has not been destructed. That is: the object constructed first and then destructed, and the destructing order is just opposite to the construction order. Children's shoes familiar with data structure should see that this is a stack model.

2, Base classes, derived classes, and member variables (sub object classes)

If a class inherits many classes and has many member objects, the execution of its constructor is extremely complex. We use code examples.

#include <iostream>

using namespace std;

// Define two base classes
class Base1
{
public:
    Base1()
    {
        cout << "+ Constructor of Base1\n";
    }
    ~Base1()
    {
        cout << "- Destructor of Base1\n";
    }
};

class Base2
{
public:
    Base2()
    {
        cout << "+ Constructor of Base2\n";
    }
    ~Base2()
    {
        cout << "- Destructor of Base2\n";
    }
};

// Define two sub object classes
class Member1
{
public:
    Member1()
    {
        cout << "+ Constructor of Member1\n";
    }
    ~Member1()
    {
        cout << "- Destructor of Member1\n";
    }
};

class Member2
{
public:
    Member2()
    {
        cout << "+ Constructor of Member2\n";
    }
    ~Member2()
    {
        cout << "- Destructor of Member2\n";
    }
};

// Define derived classes
class Derived: public Base2, public Base1
// Inherit Base2 first and then Base1
{
public:
// First define an object of Member2, and then define an object of Member1
    Member2 m2;
    Member1 m1;
    // Defines the constructor of the derived class
    Derived(): m1(), m2(), Base1(), Base2()
    // Contrary to the definition, initialize objects according to Member1, Member2, Base1 and Base2
    {
        cout << "+ Constructor of Derived\n";
    }
    // Defines the destructor of a derived class
    ~Derived()
    {
        cout << "- Destructor of Derived\n";
    }
};

int main()
{
    Derived d;
    return 0;
}

Execution result:

+ Constructor of Base2
+ Constructor of Base1
+ Constructor of Member2
+ Constructor of Member1
+ Constructor of Derived
- Destructor of Derived
- Destructor of Member1
- Destructor of Member2
- Destructor of Base1
- Destructor of Base2

Conclusion:

  1. Call the constructor of the base class first
  2. Then call the constructor of the sub object class (member variable)
  3. Finally, the constructor of the derived class is called.
  4. The calling order has nothing to do with the initialization list (Derived(): m1(), m2(), Base1(), Base2()) given after the colon of the constructor of the derived class. Initialization is carried out according to the order of inheritance and the order defined in the variable class. Inherit Base2 first and construct Base2 first. Define M2 first and construct M2 first.
  5. Destructors are still called in the reverse order of constructors.

(this paragraph is a little windy. You can pause and think about it)

Then can't we combine the two parts? A cancer C + + problem?
Example write the output of the following program.

#include <iostream>

using namespace std;

class Base1
{
public:
    Base1()
    {
        cout << "+ Constructor of Base1\n";
    }
    ~Base1()
    {
        cout << "- Destructor of Base1\n";
    }
};

class Base2
{
public:
    Base2()
    {
        cout << "+ Constructor of Base2\n";
    }
    ~Base2()
    {
        cout << "- Destructor of Base2\n";
    }
};

class Member1
{
public:
    Member1()
    {
        cout << "+ Constructor of Member1\n";
    }
    ~Member1()
    {
        cout << "- Destructor of Member1\n";
    }
};

class Member2
{
public:
    Member2()
    {
        cout << "+ Constructor of Member2\n";
    }
    ~Member2()
    {
        cout << "- Destructor of Member2\n";
    }
};

class Derived: public Base2, public Base1
{
public:
    Member2 m2;
    Member1 m1;
    Derived(): m1(), m2(), Base1(), Base2()
    {
        cout << "+ Constructor of Derived\n";
    }
    ~Derived()
    {
        cout << "- Destructor of Derived\n";
    }
};

Derived global;

void function()
{
    static Member1 m1;
    {
        Base2 b2;
        Base1 b1;
    }
    static Member2 m2;
}

int main()
{
    for(int i = 1; i < 4; ++i) function();
    return 0;
}

answer:

+ Constructor of Base2
+ Constructor of Base1
+ Constructor of Member2
+ Constructor of Member1
+ Constructor of Derived
+ Constructor of Member1
+ Constructor of Base2
+ Constructor of Base1
- Destructor of Base1
- Destructor of Base2
+ Constructor of Member2
+ Constructor of Base2
+ Constructor of Base1
- Destructor of Base1
- Destructor of Base2
+ Constructor of Base2
+ Constructor of Base1
- Destructor of Base1
- Destructor of Base2
- Destructor of Member2
- Destructor of Member1
- Destructor of Derived
- Destructor of Member1
- Destructor of Member2
- Destructor of Base1
- Destructor of Base2

(I hope I don't have this cancer problem in my C + + final exam...)

reference resources: https://en.cppreference.com/w/cpp/language/constructor

Topics: C++ Back-end Class inheritance