C/C++ Programming: Initialization List

Initialization list

What is the initialization list

Unlike other functions, in addition to the name, parameter list, and function body, the constructor can also have an initialization list. The initialization list :starts with a series ,of initialization fields separated by a series . such as:

struct foo
{
    
    
    string name ;
    int id ;
    foo(string s, int i):name(s), id(i){
    
    } ; // 初始化列表
};

The two execution phases of the constructor

The execution of the constructor can be divided into two phases, the initialization phase and the calculation phase, the initialization phase is earlier than the calculation phase

Initialization phase

All 类类型members will be initialized in the initialization phase, even if the member does not appear in the initialization list of the constructor

Calculation phase

Generally used to perform assignment operations in the function body, the following code defines two structures, among which Test1 has a constructor, a copy constructor and an assignment operator, in order to facilitate viewing the results. Test2 is a test class, which takes the objects of Test1 as members. Let's see how the constructor of Test2 is executed.

#include <iostream>
using namespace std;
struct Test1
{
    
    
    Test1() // 无参构造函数
    {
    
    
        cout << "Construct Test1" << endl ;
    }

    Test1(const Test1& t1) // 拷贝构造函数
    {
    
    
        cout << "Copy constructor for Test1" << endl ;
        this->a = t1.a ;
    }

    Test1& operator = (const Test1& t1) // 赋值运算符
    {
    
    
        cout << "assignment for Test1" << endl ;
        this->a = t1.a ;
        return *this;
    }

    int a ;
};

struct Test2
{
    
    
    Test1 test1 ;
    Test2(Test1 &t1)  // 先调用无参构造函数,然后调用赋值运算符
    {
    
    
        test1 = t1 ;
    }
};
struct Test3
{
    
    
    Test1 test1 ;
    Test3(Test1 &t1): test1(t1 ){
    
    }   // 初始化列表调用的是拷贝构造函数
};
int main(){
    
    
    Test1 t1;   // // 无参构造函数
    printf("\n");
    Test2 t2(t1);
    printf("\n");
    Test3 t3(t1);
}

Insert picture description here

To explain, the first line of output corresponds to the first line of the calling code, which constructs a Test1 object. The second line of output corresponds to the code in the Test2 constructor. The object test1 is initialized with the default constructor. This is the so-called initialization phase. The third line of output corresponds to the assignment operator of Test1, and the assignment operation is performed on test1. This is the so-called calculation phase.

Initialization sequence

The initialization order of the member initializers in the list has nothing to do with the order of appearance. In fact, the actual order of initialization is as follows:

  • If the constructor is the final derived class, initialize each virtual base class in the order of appearance in the traversal from left to right (from left to right refers to the base specifier list) in the depth-first, left-to-right traversal of the base class declaration
  • Then, initialize the direct base classes in the order from left to right as they appear in the base class specifier list of this class
  • Then, in the order of declaration in the class definition, initialize each non-static member
  • Finally, point to the body of the constructor

(Note: If the order of initialization is controlled by the appearance of member initializers in different constructors, then the destructor cannot ensure that the order of destruction is the reverse of the order of construction)

#include <fstream>
#include <string>
#include <mutex>
 
struct Base {
    
    
    int n;
};   
 
struct Class : public Base
{
    
    
    unsigned char x;
    unsigned char y;
    std::mutex m;
    std::lock_guard<std::mutex> lg;
    std::fstream f;
    std::string s;
 
    Class ( int x )
      : Base {
    
     123 }, // 初始化基类
        x ( x ),      // x(成员)以 x(形参)初始化
        y {
    
     0 },      // y 初始化为 0
        f{
    
    "test.cc", std::ios::app}, // 在 m 和 lg 初始化之后发生
        s(__func__),  //__func__ 可用,因为初始化器列表是构造函数的一部分
        lg ( m ),     // lg 使用已经初始化的 m
        m{
    
    }           // m 在 lg 前初始化,即使此出它最后出现
    {
    
    }                // 空复合语句
 
    Class ( double a )
      : y ( a+1 ),
        x ( y ), // x 将在 y 前初始化,其值不确定
        lg ( m )
    {
    
    } // 基类初始化器未出现于列表中,它被默认初始化(这不同于使用 Base(),那是值初始化)
 
    Class()
    try // 函数 try 块始于包含初始化器列表的函数体之前
      : Class( 0.0 ) // 委托构造函数
    {
    
    
        // ...
    }
    catch (...)
    {
    
    
        // 初始化中发生的异常
    }
};
 
int main() {
    
    
    Class c;
    Class c1(1);
    Class c2(0.1);
}

Why use an initialization list

There are two ways to initialize the members of a class:

  • Use initialization list
  • Assignment operations in the constructor body

The use of the initialization list is mainly based on performance issues. For built-in types, such as int, float, etc., there is not much difference between using the initialization list and the initialization in the constructor body, but for class types, it is best to use the initialization list. Why? From the above test, it can be seen that using the initialization list one less process of calling the default constructor , which is very efficient for data-intensive classes

So a good principle is to use the initialization list as much as possible when you can use the initialization list.

What must be placed in the initialization list

In addition to performance issues, there are occasions when the initialization list is indispensable. The initialization list must be used in the following situations

  • Constant members, because constants can only be initialized and cannot be assigned, they must be placed in the initialization list
  • Reference type, the reference must be initialized when it is defined, and cannot be reassigned, so it must be written to the initialization list
  • There is no default constructor for the class type, because the initialization list can be initialized without calling the default constructor, but directly by calling the copy constructor to initialize.

For a class without a default constructor, let's look at an example.

using namespace std;
struct Test1
{
    
    
    Test1(int a):i(a){
    
    }
    int i ;
};

struct Test2
{
    
    
    Test1 test1 ;
    Test2(Test1 &t1)
    {
    
    
        test1 = t1 ;
    }
};

The above code cannot be compiled because the line test1 = t1 in the constructor of Test2 is actually executed in two steps.

  1. Call the default constructor of Test1 to initialize test1

  2. Call the assignment operator of Test1 to assign a value to test1

But because Test1 does not have a default constructor, the so-called first step cannot be executed, so the compilation error occurs. The correct code is as follows, using an initialization list instead of assignment.

struct Test2
{
    
    
    Test1 test1 ;
    Test2(Test1 &t1):test1(t1){
    
    }
}

reference

Guess you like

Origin blog.csdn.net/zhizhengguan/article/details/114951794