在 C++ 中,构造是一个相当困难的话题。简单的答案是视情况而定。 Foo 是否初始化取决于 Foo 本身的定义。关于第二个问题:如何让 Bar 初始化 Foo:初始化列表就是答案。
虽然普遍的共识是 Foo 将由隐式默认构造函数(编译器生成)默认初始化,但这不需要成立。
如果 Foo 没有用户定义的默认构造函数,则 Foo 将未初始化。更准确地说:Bar 或 Foo 的每个成员缺少用户定义的默认构造函数将被编译器生成的 Bar 的默认构造函数初始化:
class Foo {
int x;
public:
void dump() { std::cout << x << std::endl; }
void set() { x = 5; }
};
class Bar {
Foo x;
public:
void dump() { x.dump(); }
void set() { x.set(); }
};
class Bar2
{
Foo x;
public:
Bar2() : Foo() {}
void dump() { x.dump(); }
void set() { x.set(); }
};
template <typename T>
void test_internal() {
T x;
x.dump();
x.set();
x.dump();
}
template <typename T>
void test() {
test_internal<T>();
test_internal<T>();
}
int main()
{
test<Foo>(); // prints ??, 5, 5, 5, where ?? is a random number, possibly 0
test<Bar>(); // prints ??, 5, 5, 5
test<Bar2>(); // prints 0, 5, 0, 5
}
现在,如果 Foo 有一个用户定义的构造函数,那么无论 Bar 是否有用户初始化的构造函数,它都会被初始化。如果 Bar 有一个用户定义的构造函数显式调用 Foo 的(可能是隐式定义的)构造函数,那么 Foo 实际上将被初始化。如果 Bar 的初始化列表没有调用 Foo 构造函数,则相当于 Bar 没有用户定义的构造函数。
测试代码可能需要一些解释。我们对编译器是否在没有用户代码实际调用构造函数的情况下初始化变量感兴趣。我们要测试对象是否被初始化。现在,如果我们只是在函数中创建一个对象,它可能会碰巧碰到一个未被触及且已经包含零的内存位置。我们想区分运气和成功,所以我们在函数中定义了一个变量并调用了两次函数。在第一次运行时,它将打印内存内容并强制更改。在对函数的第二次调用中,由于堆栈跟踪相同,因此变量将被保存在完全相同的内存位置。如果它被初始化,它将被设置为 0,否则它将保持与旧变量完全相同的值。
在每个测试运行中,打印的第一个值是初始化值(如果它实际上已被初始化)或该内存位置中的值,在某些情况下恰好为 0。第二个值只是一个测试令牌表示手动更改后内存位置的值。第三个值来自函数的第二次运行。如果变量正在初始化,它将回退到 0。如果对象未初始化,则其内存将保留旧内容。