每周技巧 #61:默认成员初始化器

本节阅读量:

本文翻译自 Abseil 官网的 Tip of the Week #61: Default Member Initializers

原文最初作为 TotW #61 发布于 2013 年 11 月 12 日。

作者:Michael Chastain

更新于 2016 年 10 月。

声明默认成员初始化

默认成员初始化器会为成员在构造时声明一个默认值,看起来像这样:

1
2
3
4
class Client {
 private:
  int chunks_in_flight_ = 0;
};

这个默认初始化器会传播到该类的所有构造函数中,甚至包括 C++ 合成的构造函数。用这种方式初始化成员,对于拥有大量数据成员的类很有用,尤其是 boolintdouble 和原始指针这类类型。这些基本类型的非静态数据成员经常漏掉初始化,最终处于未初始化状态。不过,任何类型的非静态数据成员都可以有初始化器。

默认成员初始化器也适用于没有用户编写构造函数的简单 struct 声明:

1
2
3
4
5
6
struct Options {
  bool use_loas = true;
  bool log_pii = false;
  int timeout_ms = 60 * 1000;
  std::array<int, 4> timeout_backoff_ms = { 10, 100, 1000, 10 * 1000 };
};

成员初始化覆盖

如果类构造函数初始化了一个已经拥有默认初始化器的数据成员,那么构造函数中的初始化器会取代默认初始化器:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class Frobber {
 public:
  Frobber() : ptr_(nullptr), length_(0) { }
  Frobber(const char* ptr, size_t length)
    : ptr_(ptr), length_(length) { }
  Frobber(const char* ptr) : ptr_(ptr) { }
 private:
  const char* ptr_;
  // length_ 有一个非静态类成员初始化器
  const size_t length_ = strlen(ptr_);
};

这段代码等价于旧式代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class Frobber {
 public:
  Frobber() : ptr_(nullptr), length_(0) { }
  Frobber(const char* ptr, size_t length)
    : ptr_(ptr), length_(length) { }
  Frobber(const char* ptr)
    : ptr_(ptr), length_(strlen(ptr_)) { }
 private:
  const char* ptr_;
  const size_t length_;
};

注意,前两个 Frobber 构造函数为它们的非静态变量提供了初始化器;这两个构造函数不会使用 length_ 的默认初始化器。而第三个 Frobber 构造函数没有为 length_ 提供初始化器,所以这个构造函数会使用 length_ 的默认初始化器。

和 C++ 中一贯的规则一样,所有非静态变量都按声明顺序初始化。

在 3 个 Frobber 构造函数中的前 2 个里,构造函数为 length_ 提供了初始化器。构造函数初始化器会取代默认成员初始化器,非静态类成员初始化器不会为这些构造函数贡献代码生成。

注意:较旧的文档可能把默认成员初始化器称为非静态数据成员初始化器,缩写为 NSDMI。

结论

默认成员初始化器不会让你的程序更快。它们会帮助减少遗漏带来的 bug,尤其是在有人添加新构造函数或新数据成员时。

小心不要把非静态类成员初始化器和静态类成员初始化器混淆:

1
2
3
4
class Alpha {
 private:
  static int counter_ = 0;
};

这是一个较旧的特性。counter_ 是静态的,这是一条带初始化器的静态声明。它不同于非静态类成员初始化器,就像静态成员变量不同于非静态成员变量一样。

每周技巧 #59:连接元组

上一节

每周技巧 #64:原始字符串字面量

下一节