C++ 为什么我更喜欢使用成员初始化列表?

C++ 为什么我更喜欢使用成员初始化列表?,c++,oop,object-construction,C++,Oop,Object Construction,我喜欢在构造函数中使用成员初始化列表。。。但我早就忘记了这背后的原因 在构造函数中是否使用成员初始化列表?若然,原因为何?若否,原因为何 对于班级成员来说,这没什么区别,只是风格的问题。对于属于类的类成员,它避免了对默认构造函数的不必要调用。考虑: class A { public: A() { x = 0; } A(int x_) { x = x_; } int x; }; class B { public: B() { a.x = 3

我喜欢在构造函数中使用成员初始化列表。。。但我早就忘记了这背后的原因

在构造函数中是否使用成员初始化列表?若然,原因为何?若否,原因为何

对于班级成员来说,这没什么区别,只是风格的问题。对于属于类的类成员,它避免了对默认构造函数的不必要调用。考虑:

class A
{
public:
    A() { x = 0; }
    A(int x_) { x = x_; }
    int x;
};

class B
{
public:
    B()
    {
        a.x = 3;
    }
private:
    A a;
};
在这种情况下,
B
的构造函数将调用
A
的默认构造函数,然后将
A.x
初始化为3。更好的方法是
B
的构造函数直接调用初始值设定项列表中
A
的构造函数:

B()
  : a(3)
{
}
class A
{
public:
    A(int x_) { x = x_; }
    int x;
};

class B
{
public:
    B() : a(3), y(2)  // 'a' and 'y' MUST be initialized in an initializer list;
    {                 // it is an error not to do so
    }
private:
    A a;
    const int y;
};
这将只调用
A
A(int)
构造函数,而不是其默认构造函数。在本例中,差异可以忽略不计,但是想象一下,如果您愿意的话,
A
的默认构造函数做的更多,例如分配内存或打开文件。你不会不必要地这么做的

此外,如果类没有默认构造函数,或者您有
const
成员变量,则必须使用初始值设定项列表:

B()
  : a(3)
{
}
class A
{
public:
    A(int x_) { x = x_; }
    int x;
};

class B
{
public:
    B() : a(3), y(2)  // 'a' and 'y' MUST be initialized in an initializer list;
    {                 // it is an error not to do so
    }
private:
    A a;
    const int y;
};

除了上面提到的性能原因外,如果您的类存储对作为构造函数参数传递的对象的引用,或者您的类具有常量变量,则除了使用初始值设定项列表之外,您别无选择。

在性能问题旁边,还有一个非常重要的问题,我称之为代码可维护性和可扩展性

如果
T
是POD,并且您开始首选初始化列表,那么如果有一次
T
将更改为非POD类型,您不需要更改初始化周围的任何内容,以避免不必要的构造函数调用,因为它已经过优化

如果type
T
确实有默认构造函数和一个或多个用户定义的构造函数,并且有一次您决定删除或隐藏默认构造函数,那么如果使用了初始化列表,则不需要更新用户定义的构造函数的代码,因为它们已经正确实现

const
成员或引用成员相同,假设最初
T
的定义如下:

struct T
{
    T() { a = 5; }
private:
    int a;
};
接下来,您决定将
a
限定为
const
,如果您从一开始就使用初始化列表,那么这是一个单行更改,但是如果将
T
定义为如上所述,还需要挖掘构造函数定义以删除赋值:

struct T
{
    T() : a(5) {} // 2. that requires changes here too
private:
    const int a; // 1. one line change
};

如果代码不是由一个“代码猴子”编写的,而是由一个工程师编写的,并且该工程师基于对自己所做工作的更深入考虑做出决策,那么维护就容易得多,也不容易出错,这已不是什么秘密。

在运行构造函数主体之前,调用其父类的所有构造函数,然后调用其字段的所有构造函数。默认情况下,将调用无参数构造函数。初始化列表允许您选择调用哪个构造函数以及构造函数接收哪些参数

如果有引用或常量字段,或者所使用的某个类没有默认构造函数,则必须使用初始化列表

// Without Initializer List
class MyClass {
    Type variable;
public:
    MyClass(Type a) {  // Assume that Type is an already
                     // declared class and it has appropriate 
                     // constructors and operators
        variable = a;
    }
};
 
在这里,编译器按照以下步骤创建类型为
MyClass
的对象:

  • Type
    的构造函数首先为“
    a
    ”调用
  • MyClass()
    构造函数的主体内调用“
    Type
    ”的赋值运算符进行赋值
  • 最后,“
    Type
    ”的析构函数被“
    a
    ”调用,因为它超出了范围
  • 现在考虑与<代码> MyCype()< /C>构造函数相同的代码:初始化列表:

        // With Initializer List
        class MyClass {
        Type variable;
        public:
        MyClass(Type a):variable(a) {   // Assume that Type is an already
                         // declared class and it has appropriate
                         // constructors and operators
        }
        };
     
    
    对于初始值设定项列表,编译器遵循以下步骤:

  • 调用“
    Type
    ”类的复制构造函数来初始化:
    变量(a)
    。初始值设定项列表中的参数用于直接复制构造“
    变量”

  • 类型
    ”的析构函数被“
    a
    ”调用,因为它超出了范围

  • 基类的初始化
  • 使用构造函数初始值设定项列表的一个重要原因是基类的初始化,在这里的答案中没有提到

    按照构造顺序,基类应该在子类之前构造。在没有构造函数初始值设定项列表的情况下,如果您的基类具有默认构造函数,则这是可能的,该构造函数将在进入子类的构造函数之前被调用

    但是,若基类只有参数化构造函数,那个么必须使用构造函数初始值设定项列表来确保基类在子类之前初始化

  • 初始化仅具有参数化构造函数的子对象

  • 效率

  • 使用构造函数初始值设定项列表,可以将数据成员初始化为代码中所需的确切状态,而不是首先将其初始化为默认状态,然后将其状态更改为代码中所需的状态

  • 初始化非静态常量数据成员
  • 如果类中的非静态const数据成员具有默认构造函数&您不使用构造函数初始值设定项列表,您将无法将它们初始化为预期状态,因为它们将被初始化为默认状态

  • 参考数据成员的初始化
  • 当编译器进入构造函数时,引用数据成员必须初始化,因为引用不能在以后声明和初始化。这只能通过构造函数初始值设定项列表实现。

    语法:

      class Sample
      {
         public:
             int Sam_x;
             int Sam_y;
    
         Sample(): Sam_x(1), Sam_y(2)     /* Classname: Initialization List */
         {
               // Constructor body
         }
      };
    
    需要初始化列表:

     class Sample
     {
         public:
             int Sam_x;
             int Sam_y;
    
         Sample()     */* Object and variables are created - i.e.:declaration of variables */*
         { // Constructor body starts 
    
             Sam_x = 1;      */* Defining a value to the variable */* 
             Sam_y = 2;
    
         } // Constructor body ends
      };
    
    在上面的程序中,当类的构造函数被执行时,Sam_xSam_y是c
     1. Default constructor of Sample1 class
     2. Initialization list in Sample2 class which will call the parametric constructor of Sample1 class (as per above program)
    
     class Sample3 {
        int i;         /* Member variable name : i */  
        public:
        Sample3 (int i)    /* Local variable name : i */ 
        {
            i = i;
            print(i);   /* Local variable: Prints the correct value which we passed in constructor */
        }
        int getI() const 
        { 
             print(i);    /*global variable: Garbage value is assigned to i. the expected value should be which we passed in constructor*/
             return i; 
        }
     };
    
      1. Initialization list 
      2. this operator.
    
    class NumArray {
    public:
    vector<int> preSum;
    NumArray(vector<int> nums) {
        preSum = vector<int>(nums.size()+1, 0);
        int ps = 0;
        for (int i = 0; i < nums.size(); i++)
        {
            ps += nums[i];
            preSum[i+1] = ps;
        }
    }
    
    int sumRange(int i, int j) {
        return preSum[j+1] - preSum[i];
    }
    };
    
    class NumArray {
    public:
    vector<int> preSum;
    NumArray(vector<int> nums) : preSum(nums.size()+1, 0) { 
        int ps = 0;
        for (int i = 0; i < nums.size(); i++)
        {
            ps += nums[i];
            preSum[i+1] = ps;
        }
    }
    
    int sumRange(int i, int j) {
        return preSum[j+1] - preSum[i];
    }
    };