为什么派生 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;
};
这是我的问题的一个例子:
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;
};