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

时间:2009-05-29 15:56:24

标签: c++ oop object-construction

我偏向于使用我的构造函数的成员初始化列表......但我早就忘记了这背后的原因......

您是否在构造函数中使用成员初始化列表?如果是这样,为什么?如果没有,为什么不呢?

9 个答案:

答案 0 :(得分:249)

对于POD班级成员,它没有任何区别,只是风格问题。对于类的类成员,它避免了对默认构造函数的不必要的调用。考虑:

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)
{
}

这只会调用A的{​​{1}}构造函数而不是它的默认构造函数。在这个例子中,差异可以忽略不计,但想象一下,如果你A(int)的默认构造函数做得更多,比如分配内存或打开文件。你不希望不必要地这样做。

此外,如果某个类没有默认构造函数,或者您有A成员变量,那么必须使用初始化列表:

const

答案 1 :(得分:41)

除了上面提到的性能原因,如果你的类存储对作为构造函数参数传递的对象的引用,或者你的类有const变量,那么除了使用初始化列表之外你没有任何选择。

答案 2 :(得分:15)

  1. 基类的初始化
  2. 使用构造函数初始化列表的一个重要原因是答案中未提及的是基类的初始化。

    按照构造的顺序,基类应该在子类之前构造。如果没有构造函数初始化列表,如果您的基类具有默认构造函数,则可以在进入子类的构造函数之前调用它。

    但是,如果您的基类只有参数化构造函数,那么您必须使用构造函数初始化列表来确保在子类之前初始化您的基类。

    1. 只有参数化构造函数的子对象的初始化

    2. <强>效率

    3. 使用构造函数初始化列表,您可以将数据成员初始化为代码中所需的精确状态,而不是先将它们初始化为默认状态&amp;然后将其状态更改为代码中所需的状态。

      1. 初始化非静态const数据成员
      2. 如果类中的非静态const数据成员具有默认构造函数&amp;如果您没有使用构造函数初始化列表,则无法将它们初始化为预期状态,因为它们将初始化为默认状态。

        1. 参考数据成员的初始化
        2. 当编译器进入构造函数时,必须初始化引用数据成员,因为引用不能被声明为&amp;稍后初始化。这仅适用于构造函数初始化列表。

答案 3 :(得分:8)

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

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

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

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

struct T
{
    T() { a = 5; }
private:
    int a;
};

接下来,如果您从头开始使用初始化列表,那么您决定限定为const,那么这是单行更改,但是如上所述定义了T,它还需要挖掘构造函数定义以删除赋值:

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

如果代码不是由“代码猴”编写,而是由工程师根据对他正在做的事情的深入考虑做出决策,那么维护变得容易且容易出错也不是秘密。

答案 4 :(得分:5)

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

如果您有引用或const字段,或者如果其中一个类没有默认构造函数,则必须使用初始化列表。

答案 5 :(得分:2)

// 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类型的对象      1.首先为“a”调用类型的构造函数。
    2.在MyClass()构造函数体内调用“Type”的赋值运算符以赋予

variable = a;
  1. 最后,“Type”的析构函数被称为“a”,因为它超出了范围。

    现在考虑使用带有初始化列表

    的MyClass()构造函数的相同代码
    // 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
    }
    };
    

    使用初始化列表,以下步骤后面是编译器:

    1. 调用“Type”类的复制构造函数初始化:variable(a)。初始化列表中的参数用于直接复制构造“变量”。
    2. “类型”的析构函数因为超出范围而被称为“a”。

答案 6 :(得分:1)

只需添加一些其他信息,以演示成员初始化列表可能产生的差异。在leetcode 303 Range Sum Query-Immutable https://leetcode.com/problems/range-sum-query-immutable/中,您需要在其中构造并将具有一定大小的向量初始化为零。这是两种不同的实现方式和速度比较。

没有成员初始化列表,要获得AC,我要花费 212毫秒

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];
}
};

现在使用成员初始化列表,获取AC的时间约为 108毫秒。通过这个简单的示例,很明显,成员初始化列表更加高效。所有测量均来自LC的运行时间。

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];
}
};

答案 7 :(得分:1)

如《 C ++核心准则》 C.49: Prefer initialization to assignment in constructors中所述 这样可以避免不必要地调用默认构造函数。

答案 8 :(得分:0)

语法:

  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_x Sam_y 。然后在构造函数体中定义那些成员数据变量。

用例:

  1. Class
  2. 中的Const和Reference变量

    在C中,变量必须在创建过程中定义。在C ++中,我们必须使用初始化列表在对象创建期间初始化Const和Reference变量。如果我们在创建对象后进行初始化(内部构造函数体),我们将得到编译时错误。

    1. Sample1(base)类的成员对象,它们没有默认构造函数

       class Sample1 
       {
           int i;
           public:
           Sample1 (int temp)
           {
              i = temp;
           }
       };
      
        // Class Sample2 contains object of Sample1 
       class Sample2
       {
        Sample1  a;
        public:
        Sample2 (int x): a(x)      /* Initializer list must be used */
        {
      
        }
       };
      
    2. 为派生类创建对象时,将在内部调用派生类构造函数并调用基类构造函数(默认)。如果基类没有默认构造函数,则用户将收到编译时错误。为了避免,我们必须要么

       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)
      
      1. 类构造函数的参数名称和类的Data成员相同:

         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; 
            }
         };
        
      2. 众所周知,如果两个变量具有相同的名称,则具有最高优先级的局部变量然后是全局变量。在这种情况下,程序考虑“i”值{左侧和右侧变量。即:i = i}作为Sample3()构造函数中的局部变量,而Class成员变量(i)得到了覆盖。为避免这种情况,我们必须使用

          1. Initialization list 
          2. this operator.