为什么我应该更喜欢使用成员初始值设定项列表?
我偏向于在构造函数中使用成员初始值设定项列表,但我早已忘记了其背后的原因。
您是否在构造函数中使用成员初始值设定项列表? 如果是这样,为什么? 如果没有,为什么不呢?
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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
语法:
需要初始化列表:
在上面的程序中,当执行类的构造函数时,创建了Sam_x和Sam_y。 然后在构造函数体中定义这些成员数据变量。
用例:
在 C 语言中,变量必须在创建期间定义。 与 C++ 中相同,我们必须在对象创建期间使用初始化列表来初始化 Const 和 Reference 变量。 如果我们在对象创建后(在构造函数体内)进行初始化,我们将得到编译时错误。
Sample1(基)类的成员对象没有默认构造函数
在为派生类创建对象时,它将在内部调用派生类构造函数并调用基类构造函数(默认)。 如果基类没有默认构造函数,用户将收到编译时错误。 为了避免这种情况,我们必须让
类构造函数的参数名称和类的数据成员相同:
众所周知,如果两个变量具有相同的名称,则局部变量的优先级最高,然后是全局变量。 在这种情况下,程序考虑“i”值{左侧和右侧变量。 即: i = i} 作为 Sample3() 构造函数中的局部变量,并且类成员变量 (i) 被覆盖。 为了避免,我们必须使用
Syntax:
Need of Initialization list:
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:
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.
Member objects of Sample1 (base) class which do not have default constructor
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
Class constructor’s parameter name and Data member of a Class are same:
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
只是添加一些附加信息来演示成员初始化列表可以产生多大的差异。 在 leetcode 303 范围求和查询 - 不可变中, https://leetcode.com/problems /range-sum-query-immutable/,您需要在其中构造并初始化为零具有一定大小的向量。 下面是两种不同的实现方式和速度的比较。
没有成员初始化列表,要获得 AC,我花费了大约212 毫秒。
现在使用成员初始化列表,获取AC的时间约为108 ms。 通过这个简单的例子,很明显,成员初始化列表效率更高。 所有测量均来自 LC 的运行时间。
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.
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.
对于普通类型数据成员来说,这没有什么区别,只是风格问题。 对于属于类的类成员,它可以避免对默认构造函数的不必要的调用。 考虑:
在这种情况下,
B
的构造函数将调用A
的默认构造函数,然后将ax
初始化为3. 更好的方法是让
B
的构造函数直接调用初始化列表中A
的构造函数:这只会调用
A
的 构造函数A(int)
构造函数,而不是其默认构造函数。 在此示例中,差异可以忽略不计,但想象一下,如果您愿意,A
的默认构造函数会执行更多操作,例如分配内存或打开文件。 不必要的话你不会想这么做的。此外,如果类没有默认构造函数,或者您有 const 或引用数据成员,则您必须使用初始值设定项列表:
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:
In this case, the constructor for
B
will call the default constructor forA
, and then initializea.x
to3
. A better way would be forB
's constructor to directly callA
's constructor in the initializer list:This would only call
A
'sA(int)
constructor and not its default constructor. In this example, the difference is negligible, but imagine if you will thatA
'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:除了上面提到的性能原因之外,如果您的类存储对作为构造函数参数传递的对象的引用,或者您的类具有 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.
使用构造函数初始值设定项列表的一个重要原因(此处答案中未提及)是基类的初始化。
根据构建顺序,基类应在子类之前构建。 如果没有构造函数初始值设定项列表,如果您的基类具有默认构造函数,则在进入子类的构造函数之前将调用该默认构造函数,这是可能的。
但是,如果您的基类只有参数化构造函数,那么您必须使用构造函数初始化列表来确保您的基类在子类之前初始化。
只有参数化构造函数的子对象的初始化
效率
使用构造函数初始值设定项列表,您可以将数据成员初始化为代码中所需的确切状态,而不是首先将它们初始化为默认状态& 然后将它们的状态更改为您在代码中需要的状态。
如果类中的非静态常量数据成员具有默认构造函数& 如果您不使用构造函数初始值设定项列表,则无法将它们初始化为预期状态,因为它们将被初始化为默认状态。
当编译器进入构造函数时,必须初始化引用数据成员,因为引用不能只是声明和引用。 稍后初始化。 这仅适用于构造函数初始值设定项列表。
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.
Initialization of Subobjects which only have parameterized constructors
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.
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.
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.
除了性能问题之外,还有一个非常重要的问题,我称之为代码可维护性和可扩展性。
如果
T
是 POD 并且您开始更喜欢初始化列表,那么如果一次T
将更改为非 POD 类型,您将不需要更改初始化周围的任何内容以避免不必要的构造函数调用,因为它已经被优化了。如果类型
T
确实具有默认构造函数和一个或多个用户定义的构造函数,并且一次您决定删除或隐藏默认构造函数,那么如果使用了初始化列表,则无需更新代码用户定义的构造函数,因为它们已经正确实现。与
const
成员或引用成员相同,假设最初T
定义如下:接下来,您决定将
a
限定为const
,如果您从一开始就使用初始化列表,那么这是单行更改,但是具有如上定义的T
,还需要挖掘构造函数定义以删除赋值:如果代码不是由“代码猴子”编写,而是由基于对自己正在做的事情的更深入考虑做出决策的工程师编写,那么维护会更容易并且更不容易出错,这已经不是什么秘密了。
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 timeT
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 initiallyT
is defined as follows:Next, you decide to qualify
a
asconst
, if you would use initialization list from the beginning, then this was a single line change, but having theT
defined as above, it also requires to dig the constructor definition to remove assignment: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.
在运行构造函数的主体之前,将调用其父类的所有构造函数,然后调用其字段的构造函数。 默认情况下,调用无参构造函数。 初始化列表允许您选择调用哪个构造函数以及构造函数接收哪些参数。
如果您有引用或 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.
这里编译器按照以下步骤创建
MyClass
类型的对象:a
”调用Type
的构造函数。MyClass()
构造函数体内调用“Type
”的赋值运算符进行赋值。Type
”的析构函数被“a
”调用,因为它超出了范围。现在考虑使用具有初始值设定项列表的
MyClass()
构造函数的相同代码:对于初始值设定项列表,编译器将执行以下步骤:
复制“
Type”类被调用来初始化:
variable(a)
。 初始化列表中的参数用于直接复制构造“变量
”。“
Type
”的析构函数被“a
”调用,因为它超出了范围。Here compiler follows the following steps to create an object of type
MyClass
:Type
’s constructor is called first for “a
”.Type
” is called inside the body ofMyClass()
constructor to assign.Type
” is called for “a
” since it goes out of scope.Now consider the same code with
MyClass()
constructor with an Initializer List:With the Initializer List, the following steps are followed by the compiler:
Copy constructor of “
Type
” class is called to initialize :variable(a)
. The arguments in the initializer list are used to copy construct “variable
” directly.Destructor of “
Type
” is called for “a
” since it goes out of scope.正如 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.