天天看点

本周小贴士#61:默认的成员初始化器声明默认成员初始化成员初始化器的重写结论

作为Totw#61最初始发表于2013年11月12日

由Michael Chastain([email protected])创作

更新于2016年10月

声明默认成员初始化

默认的成员初始化器在构造函数之前为成员声明默认值,如下所示:

class Client {
 private:
  int chunks_in_flight_ = 0;
};
           

默认初始化器会传播到该类的所有的构造函数,甚至C++合成的构造函数。用这种方式初始化有许多数据成员的类是非常有用的,尤其是像bool、int、double和原生指针这类数据成员。这些基础类型的非静态数据成员经常从空隙中溜走,最终没有初始化。不过,任意类型的非静态数据成员都可能有初始化器。

默认成员初始化器对于那些用户没有编写构造函数的简单结构体的声明也是有用的:

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 };
};
           

成员初始化器的重写

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

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_);
};
           

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

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_提供初始化器。构造函数初始化器替代默认的成员初始化器——非静态类成员初始化器不会为构造函数增加代码的生成。

注意:早期的文档可能认为默认的成员初始化器是非静态数据成员初始化器,缩写为NSDMIs。

结论

默认的成员初始化器不会让你的程序变得更快。它们将帮助减少来自遗漏的错误,尤其是当某人添加新的构造函数或新的数据成员时的遗漏。

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

class Alpha {
 private:
  static int counter_ = 0;
};
           

这是一个较老的特性。counter_是静态的,这是一个带有初始化器的静态声明。这与非静态类成员初始化器是不同的,正如静态成员变量不同于非静态成员变量。

继续阅读