正如其他两个答案已经提到的那样,对象的类型随着析构函数的执行而改变。一旦析构函数完成,该类型的对象就不再存在,只有它的基本子对象存在(直到它们的析构函数完成)。
这个答案的原因是提出一个有趣的实验,这段代码的输出会是什么?(哦,好吧,所有三个答案都已经告诉你了,但实验本身很有趣):
#include <iostream>
struct base {
static void print_type( base const & b ) { // [1]
std::cout << b.type() << std::endl;
}
virtual std::string type() const { // [2]
return "base";
}
virtual ~base() { print_type( *this ); }
base() { print_type( *this ); }
};
struct derived : base {
std::string type() const {
return "derived";
}
~derived() { print_type( *this ); }
derived() { print_type( *this ); }
};
struct most_derived : derived {
std::string type() const {
return "most_derived";
}
~most_derived() { print_type( *this ); }
most_derived() { print_type( *this ); }
};
int main() {
most_derived md;
base::print_type( md );
}
笔记:
print_type
为了更有趣,在构造函数中还添加了对的调用。该函数用作在特定时间点验证对象的动态类型。该函数print_type
(可以是一个独立的函数,并在不同的翻译单元中实现——以避免编译器看到它的内部)。在编译函数时,编译器无法知道它是从构造函数内部、析构函数内部还是外部调用,因此生成的代码必须使用动态调度机制,并在每个点被调度到最终覆盖器时间。
至于代码的有效性由 §12.7/2 保证:
显式或隐式地将指向 X 类对象的指针(左值)转换为指向 X 的直接或间接基类 B 的指针(引用),X 的构造及其所有直接或间接基类的构造从 B 直接或间接派生的那些应该已经开始并且这些类的销毁应该没有完成,否则转换会导致未定义的行为。要形成指向对象 obj 的直接非静态成员的指针(或访问其值),obj 的构造应已开始且其销毁不应完成,否则指针值的计算(或访问成员值)导致未定义的行为。
base&
对调用的转换print_type
是有效的,因为它们是在每个对象的构造开始之后和每个对象的销毁完成之前执行的(每个都指most_derived
程序中的每个子对象)。