在没有虚拟析构函数的情况下通过指向基的指针删除对象

deleting object through pointer to base without virtual destructor

我有代码:

class A1 {
public:
    A1() { cout << "A1"; }
    virtual ~A1() { cout << "~A1"; }
};

class A2 {
public:
    A2() { cout << "A2"; }
    ~A2() { cout << "~A2"; }
};

class B : public A1, A2 {
public:
    B() { cout << "B"; }
    ~B() { cout << "~B"; }
};

int main() {
    A1* pa1 = new B;
    delete pa1;

    return 0;
}

它工作得很好,因为我使用了虚拟析构函数。输出:A1A2B~B~A2~A1 符合预期。 然后我删除 virtual:

class A1 {
public:
    A1() { cout << "A1"; }
    ~A1() { cout << "~A1"; }
};

class A2 {
public:
    A2() { cout << "A2"; }
    ~A2() { cout << "~A2"; }
};

class B : public A1, A2 {
public:
    B() { cout << "B"; }
    ~B() { cout << "~B"; }
};

int main() {
    A1* pa1 = new B;
    delete pa1;

    return 0;
}

它输出的是:A1A2B~A1,我不明白。我认为它应该输出 A1A2B,因为 ~B() 不会输出 运行,而且我们没有 A1 的实例,所以不应调用 ~A1()以及。还有,为什么只有~A1()是运行ning,没有~A2()?为什么会这样?

如果没有 virtual,析构函数将根据指针的声明类型进行静态解析。编译器看到一个 A1 *,所以 delete pa1 将调用 ~A1 析构函数,而不是其他任何东西。

we don't have an instance of A1, so ~A1() should not be invoked as well.

B 派生自 A1,因此 A1 的实例作为每个 B 的子对象存在。否则 A1* pa1 = new B; 甚至无法编译。

And then, why only ~A1() is running, without ~A2()? Why does it behave like this?

因为指针声明为A1 *。指向的实际对象是 B 并不重要,因为析构函数不是虚拟的。

同样,如果您将 A2 继承更改为 public 并声明 A2 *pa2 = new B;,那么 delete pa2 将调用 ~A2,而不会调用其他任何东西。


[ EDIT ] 以上试图回答编译器如何解析析构函数调用的直接问题。然而,一个重要的警告是,当基 class 的析构函数是 not[=47 时,通过基指针删除指向派生类型的指针在技术上是 UB(undefined behavior) =] 虚拟。引用自 C++ 标准 5.3.5/3:

In the first alternative (delete object), if the static type of the object to be deleted is different from its dynamic type, the static type shall be a base class of the dynamic type of the object to be deleted and the static type shall have a virtual destructor or the behavior is undefined.

实际上,许多实现将允许非虚拟析构函数在某些简单情况下仍按预期“工作”,例如非多态类型之间的单一继承。在多重继承情况下(例如,上面的 pa1),甚至可能适用于指向 first 基 class 的指针。但严格来说它仍然是 UB,并且在某些情况下它几乎肯定会失败,例如指向第一个基数 class 而非第一个基数的指针(参见此类错误 here 的示例)。