销毁对象是否会停止后期绑定?

时间:2021-03-26 01:37:20

标签: c++ inheritance polymorphism virtual-functions late-binding

考虑以下 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 显示”消息。

我不明白?销毁对象会阻止后期绑定吗?

1 个答案:

答案 0 :(得分:4)

销毁一个对象使得继续调用该对象的成员函数是非法的。您的代码的行为是未定义的,并且您观察到的任何行为都无法在同一程序的运行中持续存在,更不用说不同的编译器标志或完全不同的实现了。

但是,我们可以猜测为什么您会看到您报告的特定行为,并理解我们将来不应继续编译和运行此类代码,因为无法保证其行为。

您的编译器很可能正在对 c1.display() 调用进行去虚拟化,因为它可以看到 c1 的定义(因此知道其动态类型是 C)。因此,生成的代码可能根本不查询 vtable。这解释了为什么即使对象已被销毁,您仍会看到“C 显示”。

c2 的情况下,对象是通过引用传递的,并且编译器可能没有足够积极地内联以对最终的 display 调用进行去虚拟化。如果它在 c2 销毁后查询 vtable,它可能会找到 A 的 vtable,因为 C 对象的销毁过程最终必须将 A 子对象的 vptr 重置为在执行 A 之前指向 A::~A 虚表(以防 A::~A 调用任何虚函数)。这解释了“A 显示”消息。

相关问题