为什么派生 class 的构造函数应该在其初始化列表中使用基类的默认构造函数?

Why should a derived class's constructor use the base's default constructor in it's initializer list?

这是我的问题的一个例子:

class MyBaseClass
{
 public:
    MyBaseClass(): my_bool(false), my_value(0)
    {}
    MyBaseClass(bool b, int i): my_bool(b), my_value(i)
    {}

 private:
    bool my_bool;
    int my_value;
}

class MyDerivedClass1 : public ::MyBaseClass
{
 public:
    MyDerivedClass1(double d): my_double(d)
    {}
 private:
    double my_double;
}

class MyDerivedClass2 : public ::MyBaseClass
{
 public:
    MyDerivedClass2(double d): MyBaseClass(), my_double(d)
    {}
 private:
    double my_double;
}

为什么 MyDerivedClass1 不是初始化派生 class 的好方法,而必须像 MyDerivedClass2 那样显式初始化基础 class?

我想我不明白为什么我不能只依赖 C++ 调用我的基本构造函数?我知道如果我想让它们初始化为不同的东西,我必须在我的初始化列表中调用另一个构造函数,但我只想调用基本构造函数。

在初始化列表中提供默认构造的基 class 与不提供没有区别。如果完全是你的风格,你用什么。 (或公司)

一般来说,假设您始终初始化成员,我会说您有 2 个选择(让我们将构造函数排除在范围之外)。

选项 1:初始化 init-list 中的所有成员。

如果您与 C++98 兼容,则应使用此选项。它的优点是您可以在一个列表中定义所有构造参数,从而便于搜索。 (除非您的成员超过 50 人)

此选项的缺点是当您有多个构造函数时会出现大量重复。

有了这个,你可以为他们提供 3 个变体:

  • 跳过默认初始化的 classes,这会使列表更短,但很难检查您是否打算 'forget' 它。 (考虑用指向它的指针替换 class)
  • 默认初始化所有成员,这使得列表更长,但清楚地表明了意图
  • 明确提供您正在初始化的class,通过使用临时
  • 复制构造

选项 2: 在声明时初始化所有成员,构造函数参数除外

此选项假定您在 class 声明中初始化所有内容。同样,您可以显式调用或不调用默认构造函数,最好使用大括号初始化,因为圆括号被解释为函数声明。

在初始化列表中你只需要放置链接到构造参数的成员。

此选项的优点是可读性(尤其是对于大型 classes)。缺点是这将您的编译器选项限制为 'modern' 个编译器。

基础classes

如果我们再次考虑基础 classes 并将它们视为成员,一致的方法是为选项 1 明确声明它们,而不为选项 2 编写它们。

就我个人而言,我喜欢选项 2,因为我经常遇到 class 成员过多的情况,使用此方法手动检查是否所有成员都已初始化会更容易。

然而,由于遗留问题,选项 1 经常被使用,以保持代码的一致性。

POD

值得一提的是 PODs(普通旧数据类型),如 int、double ... 如果你不初始化它们,你会得到一些随机数据。这使得显式初始化它们变得很重要,无论您使用什么方法。

结论

所以说到底,这只是风格问题,没有功能差异。

虽然在大多数情况下没有语义差异并且问题主要是风格之一,但有一种情况是不同的:当基 class 的默认构造函数不是用户时-提供。

区别在于不在成员初始化器列表中的基是默认初始化,而显式写Base() value-initializes 它,如果默认构造函数不是用户提供的,它将执行零初始化。

因此:

struct A { int i; };
struct B : A {
    B() : j(i) {}  // may be undefined behavior
    int j;
};

struct C : A {
    C() : A(), j(i) {} // OK; both i and j are zero
    int j;
};