为啥我更喜欢使用成员初始化列表?
Posted
技术标签:
【中文标题】为啥我更喜欢使用成员初始化列表?【英文标题】:Why should I prefer to use member initialization lists?为什么我更喜欢使用成员初始化列表? 【发布时间】:2009-05-29 15:56:23 【问题描述】:我偏爱在构造函数中使用成员初始化列表……但我早就忘记了这背后的原因……
您在构造函数中使用成员初始化列表吗?如果是这样,为什么?如果不是,为什么不呢?
【问题讨论】:
这里列出的原因...https://www.geeksforgeeks.org/when-do-we-use-initializer-list-in-c/ 【参考方案1】:对于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
的A(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;
;
【讨论】:
重要的参考案例也是必须的 为什么不使用“a(3);”或“a = A(3);”在 B 的默认构造函数的主体中? 能否解释一下,POD 是什么意思? @JonasStein POD 是一组定义良好的规则,适用于简单的数据结构(而不是完整的类)。阅读常见问题了解更多信息:***.com/questions/146452/what-are-pod-types-in-c @Sergey,仍然会调用 A 的默认构造函数。【参考方案2】:除了上面提到的性能原因,如果你的类存储了对作为构造函数参数传递的对象的引用,或者你的类有 const 变量,那么除了使用初始化列表之外你别无选择。
【讨论】:
我相信 const 成员也是如此。 是的,不能使用赋值来修改const变量,所以必须初始化。【参考方案3】:-
基类初始化
使用构造函数初始化列表的一个重要原因是基类的初始化。
按照构造顺序,基类应在子类之前构造。如果没有构造函数初始化器列表,如果您的基类具有默认构造函数,该构造函数将在进入子类的构造函数之前被调用,则这是可能的。
但是,如果你的基类只有参数化的构造函数,那么你必须使用构造函数初始化列表来确保你的基类在子类之前被初始化。
初始化仅具有参数化构造函数的子对象
效率
使用构造函数初始化器列表,您可以将数据成员初始化为您在代码中需要的确切状态,而不是先将它们初始化为默认状态,然后将它们的状态更改为您代码中需要的状态。
-
初始化非静态 const 数据成员
如果您的类中的非静态 const 数据成员具有默认构造函数并且您不使用构造函数初始化器列表,您将无法将它们初始化为预期状态,因为它们将被初始化为其默认状态。
-
引用数据成员的初始化
当编译器进入构造函数时必须初始化引用数据成员,因为引用不能在以后声明和初始化。这只有在构造函数初始化列表中才有可能。
【讨论】:
【参考方案4】:除了性能问题,还有一个非常重要的问题,我称之为代码可维护性和可扩展性。
如果 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 字段,或者使用的类之一没有默认构造函数,则必须使用初始化列表。
【讨论】:
【参考方案6】:// 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
”调用。
“Type
”的赋值运算符在MyClass()
构造函数体内被调用以进行赋值。
variable = a;
-
最后,“
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
;
使用初始化列表,编译器遵循以下步骤:
调用“Type
”类的复制构造函数来初始化:variable(a)
。初始化列表中的参数用于直接复制构造“variable
”。
“Type
”的析构函数因“a
”超出范围而被调用。
【讨论】:
虽然这段代码 sn-p 可以解决问题,但包括代码中的解释确实有助于提高帖子的质量。请记住,您正在为将来的读者回答问题,而这些人可能不知道您的代码建议的原因。也请尽量不要用解释性的 cmets 挤满你的代码,这会降低代码和解释的可读性! meta.stackexchange.com/q/114762/308249 请写下您自己的理解或分享原始来源的链接(此处为 geeksforgeeks.com),而不仅仅是复制粘贴。【参考方案7】:语法:
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被创建了。然后在构造函数体中,定义那些成员数据变量。
用例:
-
类中的常量和引用变量
在 C 中,变量必须在创建期间定义。与 C++ 中的相同方式,我们必须在对象创建期间使用初始化列表来初始化 Const 和 Reference 变量。如果我们在对象创建后进行初始化(在构造函数体中),我们会得到编译时错误。
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)
类构造函数的参数名和类的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() 构造函数中的局部变量,并且类成员变量(i) 被覆盖。为避免,我们必须使用任一
1. Initialization list
2. this operator.
【讨论】:
【参考方案8】:只是为了添加一些额外的信息来展示成员初始化列表可以产生多大的差异。在 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];
;
【讨论】:
【参考方案9】:如 C++ 核心指南 C.49: Prefer initialization to assignment in constructors 中所述 它可以防止对默认构造函数的不必要调用。
【讨论】:
以上是关于为啥我更喜欢使用成员初始化列表?的主要内容,如果未能解决你的问题,请参考以下文章
为啥我不能访问派生构造函数的成员初始化列表中继承的受保护字段?