[英]How can I understand these destructors?
我对以下C ++代码感到困惑(可在http://cpp.sh/8bmp在线运行)。 它结合了我在课程中正在学习的几个概念。
#include <iostream>
using namespace std;
class A {
public:
A() {cout << "A ctor" << endl;}
virtual ~A() {cout << "A dtor" << endl;}
};
class B: public A {
public:
B() {cout << "B ctor" << endl;}
~B() {cout << "B dtor" << endl;}
void foo(){cout << "foo" << endl;}
};
int main(){
B *b = new B[1];
b->~B();
b->foo();
delete b;
return 0;
}
输出:
A ctor
B ctor
B dtor
A dtor
foo
A dtor
这是我不明白的:
foo
? delete
? delete b;
此代码会泄漏内存吗? A
的析构函数是虚拟的。 我认为不会调用子类中重载的虚函数。 为什么〜A ~A()
被调用? b->~B();
然后在foo
之后打印B dtor
行。 为什么? b->~B();
两次,则输出为: B dtor\\nA dtor\\nA dtor
。 ?? delete B;
我将得到相同的输出delete B;
用delete[] b;
。 我认为第二个是正确的,因为b
是用new[]
创建的,但这没关系,因为我只是将B
一个实例推入堆中。 那是对的吗? 很抱歉问了这么多问题,但这令我感到困惑。 如果我的个人问题被误导了,请告诉我了解每个析构函数何时运行所需的知识。
“未定义的行为”(简称UB)是允许编译器执行任何操作的位置-这通常意味着介于“崩溃”,“给出错误的结果”和“仍然执行您期望的操作”之间。 您的b->foo()
绝对是未定义的,因为它发生在您的b->~B()
调用之后,
由于您的foo
函数实际上并没有使用析构函数销毁的任何东西,因此对foo
的调用“有效”,因为没有使用任何已销毁的东西。 [这是绝对不能保证的-它只是工作而已,有点像有时候跨过马路不看就好,有时则不然。 视其路况而定,这可能不是一个好主意,或者在大多数情况下可能会奏效-但是有一个原因,人们说“向左看,向右看,向左看,然后在安全的情况下越过”(或类似的意思)那)]
在已被破坏的对象上调用delete
也是UB,所以再次幸运的是,它“有效”(就“不会导致程序崩溃”的意义而言)。
同样,将delete
与new []
混合使用,反之亦然是UB-再次,编译器[及其相关的运行时]可能根据情况和条件做对还是错事。
不要依赖程序中未定义的行为[1]。 它一定会回来咬你。 C和C ++有很多UB案例,因此最好至少了解一些最常见的案例,例如“销毁后使用”,“销毁后使用”等,并留意此类情况-并避免不惜一切代价!
- 为什么在调用析构函数之后可以调用
foo
?
C ++并不会阻止您脚下射击。 仅仅因为您可以做到(并且代码不会立即崩溃),并不意味着它是合法的或定义明确的。
- 为什么在调用析构函数后可以调用
delete
?
与答案#1相同。
- 如果我注释掉,
delete b;
此代码会泄漏内存吗?
是。 您必须 delete
您的new
(并delete[]
您的new[]
)。
A
的析构函数是虚拟的。 我认为不会调用子类中重载的虚函数。 为什么〜A()被调用?
我认为您想要的词是优先 ,而不是过载 。 无论如何,您不会覆盖〜A ~A()
。 注意~B()
和~A()
具有不同的名称。
析构函数有点特殊。 当派生类的析构函数完成运行时,它将隐式调用基类的析构函数。 为什么? 因为C ++标准说的就是那样。
虚拟析构函数是特殊的析构函数。 我让您多态删除一个对象。 这意味着您可以执行以下代码:
B *b = new B;
A *a = b;
delete a; // Legal with virtual destructors, illegal without virtual.
如果A
在上面的代码中没有虚拟析构函数,则不会调用~B()
,这将是未定义的行为。 使用虚拟析构函数,编译器将在delete a;
时正确调用~B()
delete a;
运行,即使a
是A*
而不是B*
。
- 如果我注释掉
b->~B();
然后在foo
之后打印B dtor
行。 为什么?
因为它在foo()
之后运行。 delete b;
在foo()
已经运行之后,隐式调用b
的析构函数。
- 如果我重复行
b->~B();
两次,则输出为:B dtor\\nA dtor\\nA dtor
。 ??
这是未定义的行为。 所以任何事情都可能发生。 是的,那是奇怪的输出。 未定义的行为很奇怪。
- 如果切换
delete B;
我将得到相同的输出delete B;
用delete[] b;
。 我认为第二个是正确的,因为b
是用new[]
创建的,但这没关系,因为我只是将B
一个实例推入堆中。 那是对的吗?
您所谓的事情很重要。 delete
和delete[]
是不同的东西。 您不能用一个代替另一个。 您必须仅在分配有new
内存上调用delete
,并在分配有new
delete[]
的内存上调用delete
new[]
。 您不能随意混合和匹配。 这样做是未定义的行为。
您应该在此代码中使用delete[]
,因为您使用了new[]
。
Q1:在被销毁的对象上调用方法是“未定义的行为”。 这意味着该标准未指定应发生的情况。 UB背后的想法是,它们应该是应用程序逻辑中的错误,但是出于性能原因,我们不强迫编译器对此做一些特殊的事情,因为如果正确执行操作,这会降低性能。
在这种情况下,由于foo()方法不依赖于b
指向的内存中的任何内容,因此它将按预期工作。 只是因为编译器没有对此进行任何测试。
Q2:这也是“未定义的行为”。 您已经看到一些奇怪的事情正在发生。 首先,不调用B析构函数,仅调用A析构函数。 发生的是,当您先前调用b->~B()
,调用了B析构函数,然后将对象的vtable更改为A vtable(这意味着对象运行时类型变为A),然后将A析构函数更改为叫。 当您调用delete b
,运行时将调用对象的虚拟析构函数A。如前所述,这是“未定义的行为”。 编译器选择生成在调用delete b
时以这种方式工作的代码,但是它可能生成了不同的代码,但仍然正确。
如果确实如此,则由于代码中的另一个错误,在调用A析构函数后可能已经做得更糟:使用delete
而不是delete[]
。 C ++规则规定必须使用operator delete[]
释放使用operator delete[]
operator new[]
分配的数组,而使用operator delete
则是“未定义的行为”。 实际上,我知道在大多数实现中,这样做都是第一次,但是很有可能破坏内存管理数据,因此将来对new
或delete
甚至有效的调用都可能崩溃或导致内存泄漏。
Q3:如果您删除该呼叫,将发生内存泄漏。 如果您继续通话,则可能会导致内存损坏。 如果使用delete[] b
可以避免内存泄漏。 仍然存在未定义的行为,因为将对已销毁的对象调用析构函数,但是由于这些析构函数不执行任何操作,因此它们可能不会对您的程序造成更多损害
问题4:这是所有析构函数的规则,而不仅仅是虚拟析构函数:它们将破坏成员对象,然后破坏代码末尾的基础对象。
Q5:因此,当编译器为B析构函数生成代码时,它将在最后添加对A析构函数的调用。 但是有一条规则:目前, this
不再是B对象,并且在对A析构函数中的任何虚拟方法的调用期间,即使是虚拟的,也必须调用A方法,而不是B方法。 因此,在调用A析构函数之前,B析构函数会将对象的动态类型从B“降级”到A(实际上,这意味着将对象的vtable设置为A vtable)。 由于编译器的目标是生成有效的代码,因此不必在A析构函数的末尾更改vtable。 请记住:在调用析构函数之后,对象上的任何方法调用都是“未定义的行为”。 优先是性能,而不是错误检测。
问题6:与问题5的答案相同,我也谈到了问题2
问题7:重要。 很多。 delete []需要知道创建的对象数量,以便可以为数组中的所有对象调用销毁器。 通常,new []的实现实际上会分配一个size_t
元素,以将数组中对象的数量存储在数组元素之前。 因此,返回的指针不是分配的块的开始,而是大小之后的位置(在32位系统上为4个字节,在64位系统上为8个字节)。 因此,第一个new B[1]
将比new B
多分配4或8个字节,而第二个delete []将需要在分配它之前将指针递减4或8个字节。 因此, delete b
和delete[] b
非常不同。
注意:编译器没有强制以这种方式实现new[]
和delete[]
。 并且某些实现提供了运行时库的版本,该库可以执行更多检查,以便更容易检测到错误。 但是,为了获得最佳性能,如果使用new[]
则必须调用delete[]
。 而且,如果您在大多数情况下在对new[]
分配的指针上调用delete
的操作出错,则第一次执行该操作不会崩溃或失败。 稍后可能会在完全合法的new
, new[]
, delete
或delete[]
操作中崩溃或失败。 这通常意味着很多人挠头想知道为什么完全正确的操作会失败。 这是“未定义行为”的真实含义
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.