破坏对象会停止后期绑定吗?
Does destructing objects stop the late binding?
考虑以下 C++ classes 继承层次结构及其预期的多态行为。
#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 显示”消息。即使在销毁 c1 对象之后也是如此。
在下半部分,我们不调用显示函数。相反,我们调用输出流友元函数,该函数将依次调用显示成员函数。在这种情况下,它一开始工作得很好;即 cout << endl << c2 << endl;打印 C 显示。这是意料之中的,因为我们通过引用将 c1 对象传递给 ostream 友元函数,因此后期绑定将负责执行哪个显示成员函数。
但是当我们做同样的事情时 cout << endl << c2 << endl;析构 c2 对象后,奇怪的是 display 成员函数不再按预期运行。它调用基本 class 显示并打印“A 显示”消息。
我没看懂?销毁对象会停止后期绑定吗?
销毁对象会使继续调用该对象的成员函数变得非法。您的代码的行为是未定义的,并且您观察到的任何行为都不能依赖于在同一程序的 运行 中持续存在,更不用说不同的编译器标志或完全不同的实现了。
但是,我们可以猜测您为什么会看到您所报告的特定行为,前提是我们不应该继续编译并且 运行 将来会出现此类代码,因为它的行为是无法保证的。
很可能你的编译器正在去虚拟化 c1.display()
调用,因为它可以看到 c1
的定义(因此知道它的动态类型是 C
)。因此,生成的代码可能根本不查询 vtable。这解释了为什么您继续看到“C 显示”,即使对象已经被销毁。
在 c2
的情况下,对象通过引用传递,编译器可能没有足够积极地内联以去虚拟化最终的 display
调用。如果它在 c2
被销毁后查询 vtable,它可能会找到 A
的 vtable,因为 C
对象的销毁过程最终必须将 A
子对象的 vptr 重置为在执行 A::~A
之前指向 A
vtable(以防 A::~A
调用任何虚函数)。这样就解释了“A 显示”消息。
考虑以下 C++ classes 继承层次结构及其预期的多态行为。
#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 显示”消息。即使在销毁 c1 对象之后也是如此。
在下半部分,我们不调用显示函数。相反,我们调用输出流友元函数,该函数将依次调用显示成员函数。在这种情况下,它一开始工作得很好;即 cout << endl << c2 << endl;打印 C 显示。这是意料之中的,因为我们通过引用将 c1 对象传递给 ostream 友元函数,因此后期绑定将负责执行哪个显示成员函数。
但是当我们做同样的事情时 cout << endl << c2 << endl;析构 c2 对象后,奇怪的是 display 成员函数不再按预期运行。它调用基本 class 显示并打印“A 显示”消息。
我没看懂?销毁对象会停止后期绑定吗?
销毁对象会使继续调用该对象的成员函数变得非法。您的代码的行为是未定义的,并且您观察到的任何行为都不能依赖于在同一程序的 运行 中持续存在,更不用说不同的编译器标志或完全不同的实现了。
但是,我们可以猜测您为什么会看到您所报告的特定行为,前提是我们不应该继续编译并且 运行 将来会出现此类代码,因为它的行为是无法保证的。
很可能你的编译器正在去虚拟化 c1.display()
调用,因为它可以看到 c1
的定义(因此知道它的动态类型是 C
)。因此,生成的代码可能根本不查询 vtable。这解释了为什么您继续看到“C 显示”,即使对象已经被销毁。
在 c2
的情况下,对象通过引用传递,编译器可能没有足够积极地内联以去虚拟化最终的 display
调用。如果它在 c2
被销毁后查询 vtable,它可能会找到 A
的 vtable,因为 C
对象的销毁过程最终必须将 A
子对象的 vptr 重置为在执行 A::~A
之前指向 A
vtable(以防 A::~A
调用任何虚函数)。这样就解释了“A 显示”消息。