类成员初始化的首选方式?

Posted

技术标签:

【中文标题】类成员初始化的首选方式?【英文标题】:Preferred way of class member initialization? 【发布时间】:2016-03-28 20:28:15 【问题描述】:
class A  public: int x[100]; ;

声明A a 不会初始化对象(被x 字段中的垃圾值看到)。 以下将触发初始化:A aauto a = A()auto a = A

是否应该优先选择这三个中的任何一个?

接下来,让我们让它成为另一个类的成员:

class B  public: A a; ;

B 的默认构造函数似乎负责 a 的初始化。 但是,如果使用自定义构造函数,我必须处理它。 以下两个选项有效:

class B  public: A a;  B() : a()   ;

或:

class B  public: A a;  B()   ;

是否应该首选两者中的任何一个?

【问题讨论】:

请澄清有关您希望使用初始化程序的上下文的问题。你只对空初始化器感兴趣吗?您是否希望为复杂对象确定更通用的方法? 【参考方案1】:

初始化

class A  public: int x[100]; ;

声明A a不会初始化对象(被垃圾看到 字段 x) 中的值。

正确的A a 是在没有初始化器的情况下定义的,并且不满足default initialization 的任何要求。


1) 以下将触发初始化:

A a;

是的;

a 执行 list initialization 如果 为空,则变为value initialization,或者如果A 是聚合,则变为aggregate initialization。 即使默认构造函数被删除也可以工作。例如A() = delete;(如果“A”仍被视为聚合) 会在缩小转换时发出警告。

2) 以下将触发初始化:

auto a = A();

是的;

这是copy initialization,其中prvalue临时是用direct initialization()构造的 如果() 为空,则使用value initialization。 没有希望aggregate initialization。 然后将prvalue 临时用于direct-initialize 对象。 Copy elision 可能并且通常用于优化副本并在适当位置构造A。 允许跳过复制/移动构造函数的副作用。 不能删除移动构造函数。例如A(A&&) = delete; 如果复制构造函数被删除,那么移动构造函数必须存在。例如A(const A&) = delete; A(A&&) = default; 不会对缩小转换发出警告。

3) 以下将触发初始化:

auto a = A

是的;

这是copy initialization,其中prvalue临时是用list initialization构造的 如果 为空,则使用value initialization,或者如果A 是一个聚合,则可能是聚合初始化。 然后将prvalue 临时用于direct-initialize 对象。 Copy elision 可能并且通常用于优化副本并在适当位置构造A。 允许跳过复制/移动构造函数的副作用。 不能删除移动构造函数。例如A(A&&) = delete; 如果复制构造函数被删除,那么移动构造函数必须存在。例如A(const A&) = delete; A(A&&) = default; 会在缩小转换时发出警告。 即使默认构造函数被删除也可以工作。例如A() = delete;(如果“A”仍被视为聚合)

是否应该优先选择这三个中的任何一个?

显然你应该更喜欢A a


成员初始化

接下来,让我们让它成为另一个类的成员:

class B  public: A a; ;

B 的默认构造函数似乎负责初始化 a.

不,这是不正确的。

“B”的隐式定义默认构造函数将调用A 的默认构造函数,但不会初始化成员。不会触发直接或列表初始化。此示例的语句 B b; 将调用默认构造函数,但会保留 A 数组的不确定值。

1) 但是,如果使用自定义构造函数,我必须处理它。这 以下两个选项有效:

class B  public: A a;  B() : a()   ;

这会起作用;

: a() 是 constructor initializer 和 a() 是成员初始化器,作为 member initializer list 的一部分。 使用direct initialization () 或者,如果() 为空,则使用value initialization。 没有希望使用聚合初始化。 不会对缩小转换发出警告。

2) 或:

class B  public: A a;  B()   ;

这会起作用;

a 现在有一个non-static data member initializer,如果您使用聚合初始化并且编译器为not fully C++14 compliant,则可能需要构造函数对其进行初始化。 成员初始化器使用list initialization 如果 为空, 可能变为value initialization,如果A 为聚合,则可能变为aggregate initialization。 如果a 是唯一成员,则不必定义默认构造函数,默认构造函数将被隐式定义。

显然你应该更喜欢第二个选项。


就个人而言,我更喜欢在任何地方都使用大括号,some exceptions 代表 auto,而构造函数可能会将其误认为 std::initializer_list

class B  public: A a; ;

std::vector 构造函数对于 std::vector<int> v1(5,10)std::vector<int> v15,10 的行为会有所不同。使用(5,10),您将获得5 个元素,每个元素的值为10,但使用5,10,您将获得两个分别包含5 和10 的元素,因为如果您使用大括号,强烈首选std::initializer_list。这在 Scott Meyers 撰写的 Effective Modern C++ 的第 7 项中得到了很好的解释。

特别是member initializer lists,可以考虑两种格式:

Direct initialization a() 如果 () 为空,则变为 value initialization。 如果 为空,List initializationa 也会变为value initialization。

幸运的是,在成员初始化列表中,没有最麻烦的解析风险。在初始化列表之外,作为一个单独的声明,A a() 将声明一个函数,而 A a 将很清楚。此外,list initialization 还具有防止缩小转换的好处。

因此,总而言之,这个问题的答案取决于您要确定的内容,这将决定您选择的形式。对于空初始化器,规则更宽容。

【讨论】:

您能否详细说明构造函数将a 中的 误认为std::initializer_list 的含义? 我的问题是专门针对空括号的。我在您指出的书中找到了答案:空大括号将调用默认 ctor,而不是具有空列表的初始化器列表 ctor。因此空的() 的行为应该完全相同。 关于空括号的有趣点。我已经更新了答案以包括一些额外的点。 说到最棘手的解析,在初始化列表之外的一个相关观察: 而不是() 可以避免它,但这仅在某些情况下是必要的。问题似乎是允许将参数列表写入到正在定义的对象旁边的ctor(即使为空),即A a()。要更正此问题,请使用A a。但是,以下也可以:A a = A()auto a = A()。现在似乎越来越多的人写auto a = A 并说“this”可以防止大多数令人烦恼的解析。这种说法可能会令人困惑,因为 在这里没有区别。 @LasseKliemann 有时形式会有所不同。我做了一些更新。

以上是关于类成员初始化的首选方式?的主要内容,如果未能解决你的问题,请参考以下文章

从基类访问子类成员的首选方式

文件流(fstream/ifstream/ofstream)作为类成员变量的初始化方式

类静态成员变量和静态成员函数的访问方式

java中类成员初始化顺序

带你掌握C++中三种类成员初始化方式

C++类成员冒号初始化以及构造函数内赋值