为什么未构造的对象可以传递给派生初始化列表中的基 class 构造函数
Why unconstructed object can be passed to base class constructor in derived initialization list
我有一个 class 层次结构,其中在派生 class 中声明了 Dog class 的一个对象。然后,该对象被提供给初始化列表中的基class。
由于 Dog class 对象不是在调用 base class 构造函数之前构造的,因此该对象应该没有任何用处。但是,我看到,该对象可以在以下代码中使用:
#include <iostream>
using namespace std;
class Dog {
public:
Dog() {cout << "\n In Dog";}
void dfoo(){cout << "\n In dfoo";};
};
class Foo {
public:
Foo(Dog d){cout << "\n In Foo";
d.dfoo();
}
};
class Bar : public Foo {
Dog d;
public:
Bar() : Foo(d) {
cout << "\n In Bar";
}
};
int main() {
cout << "\nHello";
Bar b;
}
输出为:
Hello
In Foo
In dfoo
In Dog
In Bar
输出显示 dfoo() 甚至在构造 Dog 对象之前就被调用了。这是如何工作的?不应该 'd' 是垃圾,因为它在调用 Base 构造函数之前没有初始化吗?
如果你在 clang 中编译你的代码,你会得到警告,但你会得到相同的输出。
clang++ -std=c++11 -Wall -pedantic -pthread main.cpp && ./a.out
main.cpp:21:21: warning: field 'd' is uninitialized when used here
[-Wuninitialized] Bar() : Foo(d) {
这是怎么回事?
您正在将未初始化的 Dog 对象作为参数传递给 Foo 的构造函数。由于您的构造函数接受 Dog 参数作为值,它将尝试调用 class Dog 的 复制构造函数 来创建 Dog 的对象,即使它没有被初始化。并且系统成功地在 Foo 的构造函数中使用复制构造函数创建了一个本地 Dog 对象并调用了函数 dfoo()。
实际上,您的对象 d 包含垃圾。
当您传递 d(按值)时,您隐式调用了 Dog 的复制构造函数,并通过在 Dog 类型的垃圾对象上执行复制构造函数来创建 Dog 类型的新对象。
所以你的问题真的是:为什么 Dog 的隐式复制构造函数不关心从未构造过源对象。
由于该对象甚至没有任何内容,编译器让复制构造函数关心其源代码将浪费大量精力。
我不知道其中有多少是未定义的行为(恰好有效,因为实现是健全的)与您可能被允许对未构造的对象执行的操作。
考虑这个版本的狗:
class Dog {
public:
Dog() {cout << "\n In Dog";}
Dog(Dog const& source) { cout << "\n Trying to copy a Dog\n"; source.dfoo();}
virtual void dfoo() const {cout << "\n In dfoo";};
};
现在您对它的使用可能会以您预期的方式崩溃(因为复制构造函数的源代码本身从未构造过)。
您对 Dog 的复制构造函数的调用隐藏在按值传递给 Foo 的构造函数的操作中。请注意,它发生在实际到达 Foo 的构造函数之前。 Dog 的复制构造(及其所有优点 and/or 问题)在有或没有 Dog 复制构造函数的显式定义的情况下发生。你不定义,编译器帮你定义。
理论上,您的代码会出现未定义的行为。
您正在将未初始化的 d
传递给 Foo::Foo()
。您没有发现任何问题,因为 Dog
没有任何成员变量并且 Dog::dfoo()
不依赖于任何成员变量的值。
如果您将成员变量添加到 Dog
并在 Dog::dfoo()
中使用该成员变量,您可能会看到奇怪的输出:
class Dog {
int v1;
public:
Dog() {cout << "\n In Dog";}
void dfoo(){cout << "\n In dfoo, v1: " << v1 << endl;};
};
它是垃圾。
不过不确定您期望从中看到什么明显的症状。其一,Dog
没有数据成员。
我有一个 class 层次结构,其中在派生 class 中声明了 Dog class 的一个对象。然后,该对象被提供给初始化列表中的基class。
由于 Dog class 对象不是在调用 base class 构造函数之前构造的,因此该对象应该没有任何用处。但是,我看到,该对象可以在以下代码中使用:
#include <iostream>
using namespace std;
class Dog {
public:
Dog() {cout << "\n In Dog";}
void dfoo(){cout << "\n In dfoo";};
};
class Foo {
public:
Foo(Dog d){cout << "\n In Foo";
d.dfoo();
}
};
class Bar : public Foo {
Dog d;
public:
Bar() : Foo(d) {
cout << "\n In Bar";
}
};
int main() {
cout << "\nHello";
Bar b;
}
输出为:
Hello
In Foo
In dfoo
In Dog
In Bar
输出显示 dfoo() 甚至在构造 Dog 对象之前就被调用了。这是如何工作的?不应该 'd' 是垃圾,因为它在调用 Base 构造函数之前没有初始化吗?
如果你在 clang 中编译你的代码,你会得到警告,但你会得到相同的输出。
clang++ -std=c++11 -Wall -pedantic -pthread main.cpp && ./a.out
main.cpp:21:21: warning: field 'd' is uninitialized when used here [-Wuninitialized] Bar() : Foo(d) {
这是怎么回事?
您正在将未初始化的 Dog 对象作为参数传递给 Foo 的构造函数。由于您的构造函数接受 Dog 参数作为值,它将尝试调用 class Dog 的 复制构造函数 来创建 Dog 的对象,即使它没有被初始化。并且系统成功地在 Foo 的构造函数中使用复制构造函数创建了一个本地 Dog 对象并调用了函数 dfoo()。
实际上,您的对象 d 包含垃圾。
当您传递 d(按值)时,您隐式调用了 Dog 的复制构造函数,并通过在 Dog 类型的垃圾对象上执行复制构造函数来创建 Dog 类型的新对象。
所以你的问题真的是:为什么 Dog 的隐式复制构造函数不关心从未构造过源对象。
由于该对象甚至没有任何内容,编译器让复制构造函数关心其源代码将浪费大量精力。
我不知道其中有多少是未定义的行为(恰好有效,因为实现是健全的)与您可能被允许对未构造的对象执行的操作。
考虑这个版本的狗:
class Dog {
public:
Dog() {cout << "\n In Dog";}
Dog(Dog const& source) { cout << "\n Trying to copy a Dog\n"; source.dfoo();}
virtual void dfoo() const {cout << "\n In dfoo";};
};
现在您对它的使用可能会以您预期的方式崩溃(因为复制构造函数的源代码本身从未构造过)。
您对 Dog 的复制构造函数的调用隐藏在按值传递给 Foo 的构造函数的操作中。请注意,它发生在实际到达 Foo 的构造函数之前。 Dog 的复制构造(及其所有优点 and/or 问题)在有或没有 Dog 复制构造函数的显式定义的情况下发生。你不定义,编译器帮你定义。
理论上,您的代码会出现未定义的行为。
您正在将未初始化的 d
传递给 Foo::Foo()
。您没有发现任何问题,因为 Dog
没有任何成员变量并且 Dog::dfoo()
不依赖于任何成员变量的值。
如果您将成员变量添加到 Dog
并在 Dog::dfoo()
中使用该成员变量,您可能会看到奇怪的输出:
class Dog {
int v1;
public:
Dog() {cout << "\n In Dog";}
void dfoo(){cout << "\n In dfoo, v1: " << v1 << endl;};
};
它是垃圾。
不过不确定您期望从中看到什么明显的症状。其一,Dog
没有数据成员。