本周小贴士#146:默认vs值初始化

Posted -飞鹤-

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了本周小贴士#146:默认vs值初始化相关的知识,希望对你有一定的参考价值。

作为TotW#146最初发表于2018年4月19日

由Dominic Hamon创作

“通往成功的道路总在建设当中。” —— Lily Tomlin

精要

为了安全和可读性,您应该假设标量对象在被显式设置为一个值之前不会被初始化为一个合理的值。 使用初始化器可以确保将标量值初始化为安全值。

介绍

创建对象时,它们可能会被初始化或未初始化。 未初始化的对象阅读起来并不安全,但了解对象何时未初始化并非易事。

首先要了解的是正在构建的类型是标量、聚合还是其他类型。 标量类型可以被认为是一种简单类型:整数或浮点算术对象; 一个指针; 一个枚举; 指向成员的指针; nullptr_t。 聚合类型是数组或普通类(只有公共的非静态数据成员,没有用户提供的构造函数,没有基类,没有虚拟成员函数,也没有默认成员初始化器)。

影响实例是否已初始化为可安全读取的值的另一个因素是它是否具有显式初始化程序。 即语句中的对象名称后跟 ()、 或 = 。

由于这些规则并不直观,因此要记住确保对象已初始化的最简单规则是提供初始化器。 这称为值初始化,与默认初始化不同,默认初始化是编译器对标量和聚合类型执行的操作。

用户提供的构造函数

如果使用用户定义的构造函数定义类型,则它不是聚合类型,并且通过调用构造函数的值初始化和默认初始化,初始化变得更加简单:

struct Foo 
  Foo() : v() 

  int v;
  std::string s;
;

int main() 
  Foo default_foo;
  Foo value_foo = ;
  ...

在这种情况下,尽管 Foo 有一个用户提供的构造函数,但它无法初始化 v。在这种情况下,v 再次被默认初始化,这意味着它的值是未确定的,并且读取是不安全的。

显式值初始化

通常,为了读者的利益,最好用一个值的显式初始化来替换初始化器,即使该值为 0。 这称为直接初始化,它是一种更具体的值初始化形式。

struct Foo 
  Foo() : v(0) 

  int v;
;

默认成员初始化

相比为类定义构造函数,同时仍然避免默认初始与值初始化陷阱,一个更简单的方法是在声明的时候尽可能初始化类成员:

struct Foo 
  int v = 0;
;

这确保无论 Foo 的实例如何构造, v 都将被初始化为一个确定的值。

默认成员初始化也用作文档,特别是在布尔值或非零初始值的情况下,关于什么是成员的安全初始值。

专业提示:标量零初始化

标量值在初始化后何时可以安全读取的完整规则集:

  • 该类型后跟显式 ()、 或 = 初始化程序。
  • 正在构造的类型的实例是具有上述初始化器的数组元素。 例如,new int10
  • 正在构造的类型的实例是具有禁用的默认构造函数的类的成员,并且外部对象的实例是值初始化的。
  • 正在构造的类型的实例是静态的或线程本地的。
  • 正在构造的类型的实例是具有聚合类型的类的成员,该聚合类型具有初始值设定项。

数组类型

很容易忘记在数组声明中添加显式初始化器,但这会导致特别有害的初始化问题。

int main() 
  int foo[3];
  int bar[3] = ;
  ...

foo 的每个元素都是默认初始化的,而 bar 的每个元素都将是零初始化的。

辨别默认的默认构造函数声明的一个题外话

小测验:这些风格不同的声明会影响代码的行为吗?

struct Foo 
  Foo() = default;

  int v;
;

struct Bar 
  Bar();

  int v;
;

Bar::Bar() = default;

int main() 
  Foo f = ;
  Bar b = ;
  ...

许多开发人员会合理地假设这可能会影响代码生成质量,但除此之外是一种风格偏好。 正如您可能已经猜到的那样,因为我在问,事实并非如此。

原因可以追溯到上面关于用户提供的构造函数的第一部分。 由于 Foo 的构造函数在声明时是默认的,因此它不是用户提供的。 这意味着 Foo 是一个聚合类型,而 f.v 是零初始化的。 但是,Bar 有一个用户提供的构造函数,尽管它是由编译器作为默认构造函数创建的。 由于此构造函数没有显式初始化 Bar::v,因此 b.v 将被默认初始化并且读取不安全。

推荐

  • 明确标量类型被初始化的值,而不是依赖于零初始化。
  • 将标量类型的所有实例视为具有不确定值,直到您显式初始化或分配给它们。
  • 如果一个成员有一个合理的默认值,并且该类有多个构造函数,请使用默认成员初始化器来确保它不会未初始化。 请注意,构造函数中的成员初始值设定项将覆盖默认值。

以上是关于本周小贴士#146:默认vs值初始化的主要内容,如果未能解决你的问题,请参考以下文章

本周小贴士#61:默认的成员初始化器

本周小贴士#88:初始化:=,()和{}

本周小贴士#147:负责地使用穷举witch语句

本周小贴士#147:负责地使用穷举witch语句

本周小贴士#101:返回值,引用和生命周期

本周小贴士#120:返回值是不可触碰的