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

Las*_*ann 5 initialization class-members c++11

class A { public: int x[100]; };
Run Code Online (Sandbox Code Playgroud)

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

这三个中的任何一个应该是首选吗?

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

class B { public: A a; };
Run Code Online (Sandbox Code Playgroud)

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

class B { public: A a;  B() : a() { } };
Run Code Online (Sandbox Code Playgroud)

要么:

class B { public: A a{};  B() { } };
Run Code Online (Sandbox Code Playgroud)

这两个中的任何一个都应该是首选吗?

wal*_*lly 3

初始化

class A { public: int x[100]; };
Run Code Online (Sandbox Code Playgroud)

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

CorrectA a是在没有初始化器的情况下定义的,并且不满足默认初始化的任何要求。


1)以下将触发初始化:

A a{};
Run Code Online (Sandbox Code Playgroud)

是的;

  • a{}执行列表初始化
  • 如果为空,则变为值初始化,或者如果为聚合,则可以是聚合初始化{}A
  • 即使删除默认构造函数也能正常工作。例如A() = delete;(如果“A”仍被视为聚合)
  • 将警告缩小转换范围。

2)以下将触发初始化:

auto a = A();
Run Code Online (Sandbox Code Playgroud)

是的;

  • 这是复制初始化,其中纯右值临时值是通过直接初始化 ()构造的
  • 然后使用临时纯右值直接初始化对象。
  • 复制省略可以并且通常被采用来优化复制并A就地构建。
    • 允许跳过复制/移动构造函数的副作用。
  • 移动构造函数不能被删除。例如A(A&&) = delete;
  • 如果删除复制构造函数,则必须存在移动构造函数。例如A(const A&) = delete; A(A&&) = default;
  • 不会警告缩小转换。

3)以下将触发初始化:

auto a = A{}
Run Code Online (Sandbox Code Playgroud)

是的;

  • 这是复制初始化,其中纯右值临时值是通过列表初始化 {}构造的
    • 如果为空,则使用值初始化{};如果为聚合,则可以使用聚合初始化A
    • 然后使用临时纯右值直接初始化对象。
  • 复制省略可以并且通常被采用来优化复制并A就地构建。
    • 允许跳过复制/移动构造函数的副作用。
  • 移动构造函数不能被删除。例如A(A&&) = delete;
  • 如果删除复制构造函数,则必须存在移动构造函数。例如A(const A&) = delete; A(A&&) = default;
  • 将警告缩小转换范围。
  • 即使删除默认构造函数也能正常工作。例如A() = delete;(如果“A”仍被视为聚合)

应该优先选择这三者中的哪一个?

显然你应该更喜欢A a{}.


成员初始化

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

class B { public: A a; };
Run Code Online (Sandbox Code Playgroud)

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

不,这是不正确的。

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

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

class B { public: A a;  B() : a() { } };
Run Code Online (Sandbox Code Playgroud)

这会起作用;

2) 或:

class B { public: A a{};  B() { } };
Run Code Online (Sandbox Code Playgroud)

这会起作用;

显然你应该更喜欢第二种选择。


就我个人而言,我更喜欢在任何地方使用大括号,但有一些例外auto构造函数可能将其误认为的情况std::initializer_list

class B { public: A a{}; };
Run Code Online (Sandbox Code Playgroud)

构造函数对于和 的std::vector行为会有所不同。你会得到 5 个元素,每个元素的值为 10,但你会得到两个分别包含 5 和 10 的元素,因为如果使用大括号,这是强烈推荐的。Scott Meyers 的《Effective Modern C++》第 7 条对此进行了很好的解释。std::vector<int> v1(5,10)std::vector<int> v1{5,10}(5,10){5,10}std::initializer_list

特别是对于成员初始值设定项列表,可以考虑两种格式:

幸运的是,在成员初始值设定项列表中,不存在最令人烦恼的解析的风险。在初始值设定项列表之外,作为其自身的语句,A a()将声明一个函数A a{},这将是明确的。另外,列表初始化还具有防止缩小转换范围的优点。

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