Drill down into the C + + Object Model -- member initialization list

Posted by Pascal P. on Sun, 12 Sep 2021 01:15:43 +0200

1, What is an initialization list?

There are two ways to initialize member variables of C + + classes. The first is in the constructor function body of the class, and the other is through the initialization list. For example:

Initialize in constructor body:

class Person
{
public:
    Person()
    {
        name = 0;
        age = 0;
    }
private:
    String name;
    int age;
};

Initialize with initialization list:

class Person
{
public:
    Person() : m_age(0),  m_name(0)
    {}
private:
    String m_name;
    int m_age;
};

In most cases, both methods are possible, but there are four cases where the initialization list must be used.

  1. When initializing a reference member;
  2. When initializing a const member;
  3. When a constructor of base class is called and it has a set of parameters;
  4. When the constructor of a member class is called and it has a set of parameters.

2, Differences between two initialization methods and compiler implementation

// **Initialize in constructor body**
class Person
{
public:
    Person()
    {
        name = 0;
        age = 0;
    }
private:
    String name;
    int age;
};

This program can be compiled and executed normally, but the efficiency is lost. Here, the Person constructor will generate a * String* object, initialize it, and then assign the temporary object to the user with an assignment operator_ name, and then destroy the temporary object. The following is the result of the internal expansion of the constructor:

// C + + pseudo code
Person()
{
    //Call the default constructor of String
    _name.String::String();

    //Generate temporary objects
    String temp = String(0);

    //Copy member by member_ name
    _name.String::operator=( temp );

    //Destroy temporary objects
    temp.String::~String();

    m_age = 0;
}

The initialization list is used, such as

//**Initialize with initialization list**
class Person
{
public:
    Person() :  m_age(0) , m_name(0)
    {}
private:
    String m_name;
    int m_age;
};

The result of its constructor expansion is:

// C + + pseudo code
Person()
{
    //Call the String(int) constructor
    _name.String::String(0);
    m_age = 0;
}

3, What does the compiler do with the initialization list

The compiler will operate the initialization list one by one, insert the initialization operations in the constructor in the appropriate order, and before any code written by the user. It should be noted that the order of items in the list is determined by the order of member declarations in the class, not by the order in the initialization list. In the Person class of this example_ name is declared with_ age before, so its initialization ratio_ Good morning.

4, Traps for initializing lists

The appearance disorder between "initialization order" and "item order in initialization list" will lead to the following unexpected dangers:

class X
{
public:
    X(int val): j(val), i(j)
    {}
public:
    int i;
    int j;
};

The above program code looks like setting the initial value of J to val, and then setting the initial value of i to J. The problem is that i(j) in the initialization list actually executes earlier than j(val) due to the order of declarations. However, because J is not initialized at the beginning, the execution result of i(j) will make i unable to predict its initial value. And the compiler generally does not report errors and give warnings. (only the g + + compiler will give a warning!) how to avoid this trap?
Suggestion: put the initialization operation of one member together with the other in the constructor, as follows.

    X(int val): j(val)
    { i = j;} // i is not directly initialized by construction parameters and is placed in the constructor body instead of in the initialization list

The code with trap and modified code are given below:

lass X
{
public:
    X(int val): j(val), i(j)
    {}
public:
    int i;
    int j;
};

class Y
{
public:
    Y(int val): j(val)
    {
        i = j;
    }
public:
    int i;
    int j;
};

int main()
{
    X x88888888888888(3);
    Y y(5);

    std::cout << "x.i = " << x.i << " x.j = " << x.j << std::endl;
    std::cout << "y.i = " << y.i << " y.j = " << y.j << std::endl;

    return 0;
}


Running result (vc + +):

  The above operation results illustrate two problems:

  1. The execution order of the initialization list is independent of the order of items in the list, but determined by the declaration order of member variables;
  2. The initialization code executed by the initialization list is inserted before the code written by the user. (because in Class Y, if j (val) is inserted after i=j, i must be an unpredictable value, because j has not been initialized when this sentence is executed, but the execution result is correct. It indicates that j (val) is inserted before i=j)


5, Summary


In short, the compiler will process the initialization list one by one and may reorder it to reflect the declaration order of menbers. The compiler will insert some code into the constructor and put it before any code written by the user.

  1. Initialization list and initialization in constructor can be used in most cases, except that initialization list must be used in four cases;
  2. Initializing the list is more efficient than initializing directly in the constructor;
  3. The execution order of the initialization list is determined by the declaration order of member variables, which is independent of the element order of the initialization list;
  4. In the execution order, the initialization list is executed earlier than the initialization operation in the constructor body**
     

Topics: C++