成员初始化器列表和非静态数据成员的默认成员初始化器有啥区别?
Posted
技术标签:
【中文标题】成员初始化器列表和非静态数据成员的默认成员初始化器有啥区别?【英文标题】:What's the differences between member initializer list and default member initializer on non-static data member?成员初始化器列表和非静态数据成员的默认成员初始化器有什么区别? 【发布时间】:2016-04-13 13:38:07 【问题描述】:我想了解使用一种形式而不是另一种形式(如果有的话)的区别。
Code 1(直接在变量上初始化):
#include <iostream>
using namespace std;
class Test
public:
Test()
cout<< count;
~Test();
private:
int count=10;
;
int main()
Test* test = new Test();
Code 2(在构造函数中初始化列表):
#include <iostream>
using namespace std;
class Test
public:
Test() : count(10)
cout<< count;
~Test();
private:
int count;
;
int main()
Test* test = new Test();
语义上有什么区别,还是只是句法上的区别?
【问题讨论】:
@Chiel 当前的 c++ 标准是 c++14。我认为我们可以假设这是基线,除非问题被标记为 c++03,不是吗? @RichardHodges 是的,但它仍然是一个有用的评论 @RichardHodges 你曾经在超级计算机上运行过软件吗?我不得不从我们的代码中打破这种结构,因为它在 IBM BlueGene 编译器上不起作用。我认为值得在评论中提及。 @songyuanyao 是的,那一点是完全错误的。 没有 c++14 是一回事,但没有 c++11?恐怖…… 【参考方案1】:成员初始化
在这两种情况下,我们都在谈论member initialization。 请记住,成员是在类中的sequence in which they are declared 中初始化的。
代码 2:成员初始化器列表
在第二个版本中:
Test() : count(10)
: count(10)
是构造函数初始化器 (ctor-initializer),count(10)
是作为成员初始化器列表一部分的成员初始化器。我喜欢将此视为初始化发生的“真实”或主要方式,但它并不能确定初始化的顺序。
代码 1:默认成员初始化器
在第一个版本中:
private:
int count=10;
count
有一个默认成员初始化器。这是后备选项。如果构造函数中不存在,它将用作成员初始化器,但在类中确定了用于初始化的成员序列。
来自标准的 12.6.2 初始化基和成员,第 10 项:
如果给定的非静态数据成员同时具有 大括号或相等初始化器和一个内存初始化器,初始化 执行由 mem-initializer 指定的非静态数据 成员的大括号或相等初始化器被忽略。 [示例:给定
struct A int i = / some integer expression with side effects / ; A(int arg) : i(arg) // ... ;
A(int) 构造函数将简单地将 i 初始化为 arg 的值, 并且 i 的大括号或均衡器中的副作用不会出现 地方。 ——结束示例]
另外需要记住的是,如果你引入了一个非静态数据成员初始化器,那么在 C++11 中结构体将不再被视为聚合,但这是updated for C++14。
区别
区别在于两个选项的优先级。直接指定的构造函数初始值设定项具有优先权。在这两种情况下,我们最终都会通过不同的路径得到一个成员初始化器。 最好使用默认的成员初始化器,因为 然后编译器可以使用该信息为您生成构造函数的初始化器列表,并且它可能能够进行优化。 您可以在一处按顺序查看所有默认设置。 它减少了重复。然后,您只能将异常放入手动指定的成员初始化器列表中。使用一种形式而不是另一种形式有什么区别(如果 任何)。
【讨论】:
【参考方案2】:在 C++ 核心指南(参见下面的注释 1)中,Guideline C.48 推荐第一种方法(类内初始化器。)提供的理由是:
明确要求在所有构造函数中使用相同的值。避免重复。避免维护问题。它导致最短和最有效的代码。
事实上,如果你的构造函数除了初始化成员变量什么都不做,就像你的问题一样,那么Guideline C.45 仍然更坚定,说肯定要使用类内初始化器。它解释了
使用类内成员初始化器可以让编译器为您生成函数。编译器生成的函数可以更高效。
即使我没有编写编译器,我也不会与 Stroustrup、Sutter 以及他们的数百名朋友和同事争论,因此我无法证明它更有效。尽可能使用类内初始化器。
-
如果您不熟悉这些指南,请点击链接查看示例代码和更多说明。
【讨论】:
【参考方案3】:我能想到的区别是member initializer list在default member initializer之前。
通过一个默认的成员初始化器,它只是一个大括号或 equals 包含在成员声明中的初始化器,用于 如果成员在成员初始化器列表中被省略。
如果一个成员有一个默认的成员初始化器并且也出现在 构造函数中的成员初始化列表,默认成员 初始化器被忽略。
例如:
class Test
public:
Test() // count will be 10 since it's omitted in the member initializer list
Test(int c) : count(c) // count's value will be c, the default member initializer is ignored.
private:
int count = 10;
;
【讨论】:
【参考方案4】:代码没有区别。如果您将有多个构造函数重载并且不止一个计数将是 10,那么差异就会出现。使用第一个版本,您将减少编写工作。
class Test
public:
Test() = default;
Test(int b) : b(b) // a = 1, c = 3
~Test();
private:
int a = 1;
int b = 2;
int c = 3;
;
与上面代码看起来像这样的第二个版本相反:
class Test
public:
Test() : a(1), b(2), c(3)
Test(int b) : a(1), b(b), c(3)
~Test();
private:
int a;
int b;
int c;
;
成员变量越多,差异越大。
【讨论】:
你的意思是继续写 b(10) 而不是使用你接受的 int 参数? @kfsone 是的,这是一个错字。我修好了。 为什么要在第二个版本中初始化变量?已经有初始化列表可以做到这一点。 @paizza 复制/粘贴错误 ;) 感谢您的关注。 老实说,我更喜欢第一个版本,而不是使用初始化列表(这会造成混淆)。【参考方案5】:当您在成员声明旁边进行初始化时,这仅在 C++11 之后有效,因此如果您在 C++98/03 中,则完全不能这样做。
如果值永远不会改变,您可以选择将其设为constexpr static
,然后编译器将被要求不为该值使用任何额外的存储空间(只要您不定义它)并立即使用在使用值的地方不断传播。
使用 by-declaration 语法的一个缺点是它必须在标头中,这将导致每次您想要更改标头的值时都重新编译包含标头的所有翻译单元。如果这需要很长时间,那可能是不可接受的。
另一个区别是,使用成员初始化列表可以让您更改每个构造函数的值,而使用 by-declaration 版本只允许您为所有构造函数指定一个值(尽管您可以覆盖这个值......但我会亲自避免这种情况,因为它可能会变得非常混乱!)。
顺便说一句,这里不需要使用new
来创建Test
的实例。当人们从其他语言转向该语言时,这是一个常见的错误,我想让你知道。当然,在您的示例之外执行此操作还有很多用途。
【讨论】:
由声明语法要求值是常量是不正确的。 See here @rozina 这是我以为我删除的错字,您指的是以前版本的答案吗? :) 没有。您的第一行暗示:“使用成员初始化列表语法时,您可以根据非常量(例如,构造函数参数)初始化成员。”以上是关于成员初始化器列表和非静态数据成员的默认成员初始化器有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章