首页 > 解决方案 > 通过指向基址的指针删除对象而不使用虚拟析构函数

问题描述

我有代码:

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()在运行,没有~A2()?为什么它会这样?

标签: c++destructordelete-operatorvirtual-destructor

解决方案


如果没有virtual析构函数,则根据指针的声明类型静态解析。编译器看到一个A1 *所以delete pa1会调用~A1析构函数,没有别的。

我们没有 A1 的实例,所以也不应该调用 ~A1()。

B派生自A1so 的一个实例A1作为 every 的子对象存在B。否则A1* pa1 = new B;甚至不会编译。

然后,为什么只有 ~A1() 正在运行,而没有 ~A2()?为什么它会这样?

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

同样,如果您将A2继承更改为 public 并声明A2 *pa2 = new B;thendelete pa2将调用~A2,仅此而已。


[编辑] 上面试图回答编译器如何解析析构函数调用的直接问题。然而,一个重要的警告是,当基类的析构函数不是虚拟的时,通过基指针删除指向派生类型的指针在技术上是 UB(未定义行为)。引用 C++ 标准 5.3.5/3:

在第一种选择(删除对象)中,如果要删除的对象的静态类型与其动态类型不同,则静态类型应为要删除的对象的动态类型的基类, 并且静态类型应具有虚拟析构函数 或行为未定义

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


推荐阅读