类成员初始化的首选方式?
Posted
技术标签:
【中文标题】类成员初始化的首选方式?【英文标题】:Preferred way of class member initialization? 【发布时间】:2016-03-28 20:28:15 【问题描述】:class A public: int x[100]; ;
声明A a
不会初始化对象(被x
字段中的垃圾值看到)。
以下将触发初始化:A a
或 auto 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 initializationa()
如果 ()
为空,则变为 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 有时形式会有所不同。我做了一些更新。以上是关于类成员初始化的首选方式?的主要内容,如果未能解决你的问题,请参考以下文章