【问题标题】: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() { } };

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

【问题讨论】:

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

标签: c++11 initialization class-members


【解决方案1】:

初始化

class A { public: int x[100]; };

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

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


1) 以下将触发初始化:

A a{};

是的;


2) 以下将触发初始化:

auto a = A();

是的;

  • 这是copy initialization,其中prvalue临时是用direct 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{}构造的
  • 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() { } };

这会起作用;

2) 或:

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

这会起作用;

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


就个人而言,我更喜欢在任何地方都使用大括号,some exceptions 代表 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 项中得到了很好的解释。

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

幸运的是,在成员初始化列表中,没有最麻烦的解析风险。在初始化列表之外,作为一个单独的声明,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 有时形式会有所不同。我做了一些更新。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-12-29
  • 1970-01-01
  • 2023-04-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多