考虑以下 C++ 类继承层次结构及其预期的多态行为。
#include <iostream>
using namespace std;
class A
{
public:
A () { cout << "A constructor\n"; }
virtual void display() { cout << "A display\n"; }
virtual ~A() { cout << "A destructor\n"; }
friend ostream& operator << (ostream &out, A &a) {
a.display();
return out;
}
};
class B : public A
{
public:
B () { cout << "B constructor\n"; }
virtual void display() { cout << "B display\n"; }
virtual ~B() { cout << "B destructor\n"; }
};
class C : public B
{
public:
C () { cout << "C constructor\n"; }
virtual void display() {cout << "C display\n";}
virtual ~C() { cout << "C destructor\n"; }
};
int main()
{
C c1;
cout << endl; c1.display(); cout << endl;
c1.~C();
cout << endl; c1.display(); cout << endl;
c1.~C();
cout << "=================================================" << endl;
C c2;
cout << endl << c2 << endl;
c2.~C();
cout << endl << c2 << endl;
c2.~C();
return 0;
}
我的理解是 display 成员函数是虚拟的,因此将始终如此。在主程序的顶部,它表现良好;也就是说,当我们调用 c1.display() 时,它会打印“C display”消息。即使在销毁 c1 对象之后也是如此。
在下半部分,我们不调用显示函数。相反,我们调用输出流朋友函数,该函数将依次调用显示成员函数。在这种情况下,它起初运行良好;即 cout << endl << c2 << endl;打印 C 显示。这是意料之中的,因为我们通过引用将 c1 对象传递给 ostream 好友函数,因此后期绑定将负责执行哪个显示成员函数。
但是当我们做同样的 cout << endl << c2 << endl;销毁 c2 对象后,奇怪的是显示成员函数不再按预期运行。它调用基类显示并打印“A 显示”消息。
我不明白?销毁对象会阻止后期绑定吗?
答案 0 :(得分:4)
销毁一个对象使得继续调用该对象的成员函数是非法的。您的代码的行为是未定义的,并且您观察到的任何行为都无法在同一程序的运行中持续存在,更不用说不同的编译器标志或完全不同的实现了。
但是,我们可以猜测为什么您会看到您报告的特定行为,并理解我们将来不应继续编译和运行此类代码,因为无法保证其行为。
您的编译器很可能正在对 c1.display()
调用进行去虚拟化,因为它可以看到 c1
的定义(因此知道其动态类型是 C
)。因此,生成的代码可能根本不查询 vtable。这解释了为什么即使对象已被销毁,您仍会看到“C 显示”。
在 c2
的情况下,对象是通过引用传递的,并且编译器可能没有足够积极地内联以对最终的 display
调用进行去虚拟化。如果它在 c2
销毁后查询 vtable,它可能会找到 A
的 vtable,因为 C
对象的销毁过程最终必须将 A
子对象的 vptr 重置为在执行 A
之前指向 A::~A
虚表(以防 A::~A
调用任何虚函数)。这解释了“A 显示”消息。