初始化
class A { public: int x[100]; };
Declaring A a
will not initialize the object (to be seen by garbage
values in the field x).
正确的A a
没有初始化器,不满足默认初始化的任何要求。
1) The following will trigger initialization:
A a{};
是的;
a{}
执行列表初始化,如果 {}
是空的,则变成值初始化,或者如果 A
是一个聚合体则可以是聚合初始化。
- 即使默认构造函数被删除,也可以工作。例如:
A() = delete;
(如果 'A' 仍然被认为是一个聚合体)
- 将警告狭窄转换。
2) The following will trigger initialization:
auto a = A()
是的;
- 这是复制初始化,其中使用直接初始化
()
构造prvalue临时对象,如果()
为空,则使用值初始化。
- 无法进行聚合初始化。
- 然后使用prvalue临时对象对对象进行直接初始化。
- 复制省略可能被并且通常被用来优化掉复制,并在原地构造
A
。
- 跳过复制/移动构造函数的副作用是允许的。
- 移动构造函数不能被删除。例如
A(A&&) = delete;
- 如果复制构造函数被删除,则移动构造函数必须存在。例如
A(const A&) = delete; A(A&&) = default;
- 不会警告收窄转换。
3) The following will trigger initialization:
auto a = A{}
是的;
- 拷贝省略(copy elision) 可以并且通常被用来优化掉拷贝并直接在该地方构造出 A 对象。
- 可以允许跳过拷贝/移动构造函数的副作用(side effects)。
- 移动构造函数可能不会被删除。例如:
A(A&&) = delete;
- 如果复制构造函数被删除,则必须存在移动构造函数。例如:
A(const A&) = delete; A(A&&) = default;
- 将会警告缩小类型转换(narrowing conversion)。
- 即使默认构造函数被删除,也可以使用。例如:
A() = delete;
(如果'A'仍然被认为是一个聚合对象)
这三种初始化方式中是否应该特别优先考虑哪一种?
显然你应该优先选择 A a{}
。
成员初始化(Member Initialization)
Next, let us make it a member of another class:
class B { public: A a; };
The default constructor of B
appears to take care of initialization
of a
.
不,这是不正确的。
- 'B'的隐式默认构造函数将调用
A
的默认构造函数,但不会初始化成员。没有直接或列表初始化将被触发。例如语句B b;
将调用默认构造函数,但保留了A
数组的不确定值。
1) However, if using a custom constructor, I have to take care of it. The
following two options work:
class B { public: A a; B() : a() { } };
这将起作用;
2) or:
class B { public: A a{}; B() { } };
这将起作用;
显然,您应该选择第二个选项。
个人而言,我喜欢到处使用大括号,在某些情况下使用auto
和构造函数可能会将其误认为是std :: initializer_list
的情况除外:
class B { public: A a{}; };
一个
std::vector
的构造函数对于
std::vector<int> v1(5,10)
和
std::vector<int> v1{5,10}
会有不同的行为。使用
(5,10)
,你将得到5个元素,每个元素的值都是10;但是使用
{5,10}
,你将得到两个元素,分别包含5和10,因为如果使用大括号,则
std::initializer_list
优先被采用。这在Scott Meyers的Effective Modern C++的第7条中解释得非常好。
特别针对
成员初始化列表,可以考虑两种格式:
在成员初始化列表中,幸运的是,不存在最麻烦的解析风险。作为单独语句时,
A a()
将声明一个函数,而
A a{}
则很清晰。此外,
列表初始化有防止缩小转换的好处。
因此,总之,这个问题的答案取决于你想要确保什么,这将确定你选择的形式。对于空初始化器,规则更宽松。