为什么我应该更喜欢使用成员初始值设定项列表?

发布于 2024-07-22 06:02:41 字数 103 浏览 7 评论 0原文

我偏向于在构造函数中使用成员初始值设定项列表,但我早已忘记了其背后的原因。

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

I'm partial to using member initializer lists for my constructors, but I've long since forgotten the reasons behind this.

Do you use member initializer lists in your constructors? If so, why? If not, why not?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

來不及說愛妳 2024-07-29 06:02:42

语法:

  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(基)类的成员对象没有默认构造函数

     类 Sample1  
       { 
           整数我; 
           民众: 
           样本1(内部温度) 
           { 
              我=温度; 
           } 
       }; 
    
        // 类 Sample2 包含 Sample1 的对象  
       样本2类 
       { 
        样品1a; 
        民众: 
        Sample2 (int x): a(x) /* 必须使用初始化列表 */ 
        { 
    
        } 
       }; 
      

在为派生类创建对象时,它将在内部调用派生类构造函数并调用基类构造函数(默认)。 如果基类没有默认构造函数,用户将收到编译时错误。 为了避免这种情况,我们必须让

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

     类 Sample3 { 
          整数我;   /* 成员变量名: i */   
          民众: 
          Sample3 (int i) /* 局部变量名 : i */  
          { 
              我=我; 
              打印(一);   /* 局部变量:打印我们在构造函数中传递的正确值 */ 
          } 
          int getI() 常量  
          {  
               打印(一);   /*全局变量:垃圾值赋给i。   期望值应该是我们在构造函数中传递的值*/ 
               返回我;  
          } 
       }; 
      

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

  1. Initialization list 
  2. this operator.

Syntax:

  class Sample
  {
     public:
         int Sam_x;
         int Sam_y;

     Sample(): Sam_x(1), Sam_y(2)     /* Classname: Initialization List */
     {
           // Constructor body
     }
  };

Need of Initialization list:

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

in the above program, When the class’s constructor is executed, Sam_x and Sam_y are created. Then in constructor body, those member data variables are defined.

Use cases:

  1. Const and Reference variables in a Class

In C, variables must be defined during creation. the same way in C++, we must initialize the Const and Reference variable during object creation by using Initialization list. if we do initialization after object creation (Inside constructor body), we will get compile time error.

  1. Member objects of Sample1 (base) class which do not have default constructor

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

While creating object for derived class which will internally calls derived class constructor and calls base class constructor (default). if base class does not have default constructor, user will get compile time error. To avoid, we must have either

 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. Class constructor’s parameter name and Data member of a Class are same:

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

As we all know, local variable having highest priority then global variable if both variables are having same name. In this case, the program consider "i" value {both left and right side variable. i.e: i = i} as local variable in Sample3() constructor and Class member variable(i) got override. To avoid, we must use either

  1. Initialization list 
  2. this operator.
过气美图社 2024-07-29 06:02:42

只是添加一些附加信息来演示成员初始化列表可以产生多大的差异。 在 leetcode 303 范围求和查询 - 不可变中, 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 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];
}
};

Just to add some additional info to demonstrate how much difference the member initialization list can mak. In the leetcode 303 Range Sum Query - Immutable, https://leetcode.com/problems/range-sum-query-immutable/, where you need to construct and initialize to zero a vector with certain size. Here is two different implementation and speed comparison.

Without member initialization list, to get AC it cost me about 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];
}
};

Now using member initialization list, the time to get AC is about 108 ms. With this simple example, it is quite obvious that, member initialization list is way more efficient. All the measurement is from the running time from 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];
}
};
人生百味 2024-07-29 06:02:41

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

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 的默认构造函数,然后将 ax 初始化为 3. 更好的方法是让 B 的构造函数直接调用初始化列表中 A 的构造函数:

B() : a(3) {}

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

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

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

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

For trivial type data members, it makes no difference, it's just a matter of style. For class members which are classes, then it avoids an unnecessary call to a default constructor. Consider:

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

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

In this case, the constructor for B will call the default constructor for A, and then initialize a.x to 3. A better way would be for B's constructor to directly call A's constructor in the initializer list:

B() : a(3) {}

This would only call A's A(int) constructor and not its default constructor. In this example, the difference is negligible, but imagine if you will that A's default constructor did more, such as allocating memory or opening files. You wouldn't want to do that unnecessarily.

Furthermore, if a class doesn't have a default constructor, or you have a const or reference data member, you must use an initializer list:

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

class B
{
public:
    // 'a' and 'y' MUST be initialized in an initializer list;
    // it is an error not to do so.
    B() : a(3), y(2) {}
private:
    A a;
    const int y;
};
生活了然无味 2024-07-29 06:02:41

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

Apart from the performance reasons mentioned above, if your class stores references to objects passed as constructor parameters or your class has const variables then you don't have any choice except using initializer lists.

五里雾 2024-07-29 06:02:41
  1. 基类的初始化

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

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

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

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

  2. 效率

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

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

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

  1. 引用数据成员的初始化

当编译器进入构造函数时,必须初始化引用数据成员,因为引用不能只是声明和引用。 稍后初始化。 这仅适用于构造函数初始值设定项列表。

  1. Initialization of base class

One important reason for using constructor initializer list which is not mentioned in answers here is initialization of base class.

As per the order of construction, base class should be constructed before child class. Without constructor initializer list, this is possible if your base class has default constructor which will be called just before entering the constructor of child class.

But, if your base class has only parameterized constructor, then you must use constructor initializer list to ensure that your base class is initialized before child class.

  1. Initialization of Subobjects which only have parameterized constructors

  2. Efficiency

Using constructor initializer list, you initialize your data members to exact state which you need in your code rather than first initializing them to their default state & then changing their state to the one you need in your code.

  1. Initializing non-static const data members

If non-static const data members in your class have default constructors & you don't use constructor initializer list, you won't be able to initialize them to intended state as they will be initialized to their default state.

  1. Initialization of reference data members

Reference data members must be intialized when compiler enters constructor as references can't be just declared & initialized later. This is possible only with constructor initializer list.

oО清风挽发oО 2024-07-29 06:02:41

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

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

如果代码不是由“代码猴子”编写,而是由基于对自己正在做的事情的更深入考虑做出决策的工程师编写,那么维护会更容易并且更不容易出错,这已经不是什么秘密了。

Next to the performance issues, there is another one very important which I'd call code maintainability and extendibility.

If a T is POD and you start preferring initialization list, then if one time T will change to a non-POD type, you won't need to change anything around initialization to avoid unnecessary constructor calls because it is already optimised.

If type T does have default constructor and one or more user-defined constructors and one time you decide to remove or hide the default one, then if initialization list was used, you don't need to update code of your user-defined constructors because they are already correctly implemented.

Same with const members or reference members, let's say initially T is defined as follows:

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

Next, you decide to qualify a as const, if you would use initialization list from the beginning, then this was a single line change, but having the T defined as above, it also requires to dig the constructor definition to remove assignment:

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

It's not a secret that maintenance is far easier and less error-prone if code was written not by a "code monkey" but by an engineer who makes decisions based on deeper consideration about what he is doing.

甜嗑 2024-07-29 06:02:41

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

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

Before the body of the constructor is run, all of the constructors for its parent class and then for its fields are invoked. By default, the no-argument constructors are invoked. Initialization lists allow you to choose which constructor is called and what arguments that constructor receives.

If you have a reference or a const field, or if one of the classes used does not have a default constructor, you must use an initialization list.

爱冒险 2024-07-29 06:02:41
// 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”调用Type的构造函数。
  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. Type”的析构函数被“a”调用,因为它超出了范围。

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

Here compiler follows the following steps to create an object of type MyClass:

  1. Type’s constructor is called first for “a”.
  2. The assignment operator of “Type” is called inside the body of MyClass() constructor to assign.
variable = a;
  1. And then finally the destructor of “Type” is called for “a” since it goes out of scope.

Now consider the same code with MyClass() constructor with an Initializer List:

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

With the Initializer List, the following steps are followed by the compiler:

  1. Copy constructor of “Type” class is called to initialize : variable(a). The arguments in the initializer list are used to copy construct “variable” directly.

  2. Destructor of “Type” is called for “a” since it goes out of scope.

柒七 2024-07-29 06:02:41

正如 C++ 核心指南 C.49 中所述:在构造函数中优先进行初始化而不是赋值
它可以防止对默认构造函数的不必要的调用。

As explained in the C++ Core Guidelines C.49: Prefer initialization to assignment in constructors
it prevents unnecessary calls to default constructors.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文