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


Answers:


278

对于POD类成员,这没有什么区别,这只是样式问题。对于属于类的类成员,则它避免了对默认构造函数的不必要调用。考虑:

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

class B
{
public:
    B()
    {
        a.x = 3;
    }
private:
    A a;
};

在这种情况下,for的构造函数B将为调用默认的构造函数A,然后初始化a.x为3。一种更好的方法是for B的构造A函数在初始化列表中直接调用的构造函数:

B()
  : a(3)
{
}

这只会调用AA(int)构造函数,而不是其默认构造函数。在此示例中,差异可以忽略不计,但是可以想象一下,如果您愿意的话,A默认构造函数会做更多的事情,例如分配内存或打开文件。您不想不必要地这样做。

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

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

5
对于参考的重要情况也是必须的
4pie0

5
为什么不使用“ a(3);” 或“ a = A(3);” 在B的默认构造函数的主体中?
谢尔盖

1
您能否解释一下POD的含义?
乔纳斯·斯坦

2
@JonasStein POD是一组定义良好的规则,它们与简单的数据结构(而不​​是完整的类)有关。阅读常见问题解答以了解更多信息:stackoverflow.com/questions/146452/what-are-pod-types-in-c
monkey0506

2
@Sergey,仍将调用A的默认构造函数。
瓦西利斯

44

除了上述性能原因之外,如果您的类存储对作为构造函数参数传递的对象的引用,或者您的类具有const变量,则除了使用初始化程序列表之外,您别无选择。


7
我相信const成员也是如此。
理查德·科登

是的,无法使用赋值来修改const变量,因此必须对其进行初始化。
哈琳·拉克斯

23
  1. 基类的初始化

使用构造函数初始化器列表的一个重要原因(此处未在答案中提及)是基类的初始化。

根据构造顺序,应在子类之前构造基类。如果没有基类构造函数初始化器列表,则可以在基类具有默认构造函数的情况下进行操作,该构造函数将在进入子类的构造函数之前被调用。

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

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

  2. 效率

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

  1. 初始化非静态const数据成员

如果类中的非静态const数据成员具有默认构造函数,并且您不使用构造函数初始化列表,则将它们初始化为默认状态后,将无法将它们初始化为预期状态。

  1. 初始化参考数据成员

当编译器进入构造函数时,引用数据成员必须被初始化,因为引用不能在以后声明和初始化。仅在构造函数初始化器列表中才有可能。


10

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

如果T是POD,并且您开始偏爱初始化列表,则如果一次T将更改为非POD类型,则无需在初始化周围进行任何更改,以避免不必要的构造函数调用,因为它已经被优化。

如果类型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
};

如果代码不是由“代码猴子”而是由工程师根据更深层次的考虑来做出决定来编写的,那么维护起来将变得更加容易且不会出错,这不是秘密。


5

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

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


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()构造函数的内部调用“类型”的赋值运算符进行赋值

variable = a;
  1. 最后,由于“类型”的析构函数超出范围,因此将其称为“ 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”。

2
尽管此代码段可以解决问题,但在代码外进行解释确实有助于提高您的帖子质量。请记住,您将来会为读者回答这个问题,而这些人可能不知道您提出代码建议的原因。也请尽量不要在代码中加入解释性注释,这会降低代码和解释的可读性!meta.stackexchange.com/q/114762/308249
davejal

2
请写下您自己的理解,或者仅共享原始资源的链接(在此处,geeksforgeeks.com),而不仅仅是复制粘贴。
yuvi '16

1

只是添加一些额外的信息来演示成员初始化列表可以产生多大的差异。在leetcode 303范围总和查询-不可变中,https: //leetcode.com/problems/range-sum-query-immutable/ ,您需要在其中构造并初始化为一定大小的向量并将其初始化为零。这是两种不同的实现方式和速度比较。

如果没有成员初始化列表,要获得AC,大约要花费212 ms

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 ms。通过这个简单的示例,很明显,成员初始化列表更加高效。所有测量均来自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];
}
};

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

用例:

  1. 类中的常量和引用变量

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

  1. Sample1(基)类的成员对象没有默认构造函数

     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 */
      {
    
      }
     };

在为派生类创建对象时,该对象将在内部调用派生类构造函数并调用基类构造函数(默认)。如果基类没有默认构造函数,则用户将获得编译时错误。为了避免,我们必须

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

众所周知,如果两个变量具有相同的名称,则本地变量具有最高优先级,然后是全局变量。在这种情况下,程序将考虑“ i”值{左侧和右侧变量。即:i = i}作为Sample3()构造函数中的局部变量,并且Class成员变量(i)被覆盖。为了避免,我们必须使用

  1. Initialization list 
  2. this operator.

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.