c++ - 通过指向基址的指针删除对象而不使用虚拟析构函数
问题描述
我有代码:
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()
?为什么它会这样?
解决方案
如果没有virtual
析构函数,则根据指针的声明类型静态解析。编译器看到一个A1 *
所以delete pa1
会调用~A1
析构函数,没有别的。
我们没有 A1 的实例,所以也不应该调用 ~A1()。
B
派生自A1
so 的一个实例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,并且在某些情况下它几乎肯定会失败,例如指向除第一个基类之外的基类的指针(请参阅此处的一个此类错误的示例)。
推荐阅读
- postgresql - 按组运行带有 case 语句的 sum
- javascript - 挑战:BigQuery 递归乘法和求和
- rust - XNextEvent() in Rust segfaults
- jquery - Shopify 上的 Slick.js 滑块
- python - 使用 pip 安装后没有模块命名错误
- laravel-backpack - Backpack-for-laravel: 为browse_multiple显示图片
- python - 在 Python 中使用上下文管理器和条件变量来保证线程安全
- scala - 如何删除乱序的 Spark 值
- angular - 如何用子菜单组织模块?
- java - 如何在android中动态添加小部件?