我对以下C++代码感到困惑(在http://cpp.sh/8bmp上在线运行).它结合了我在课程中学到的几个概念.
#includeusing 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()
要打电话呢?
如果我注释掉,b->~B();
那么之后B dtor
会打印这一行foo
.为什么?
如果我重复b->~B();
两次,那么输出是:B dtor\nA dtor\nA dtor
.咦?
如果我切换delete B;
,我得到相同的输出delete[] b;
.我认为第二个是正确的,因为b
是创建的new[]
,但它并不重要因为我只是将一个实例推B
送到堆中.那是对的吗?
我很抱歉问了这么多问题,但这让我很困惑.如果我的个别问题被误导,那么告诉我在理解每个析构函数运行时需要了解的内容.
"未定义的行为"(简称UB)是允许编译器执行任何操作的地方 - 这通常意味着介于"崩溃","给出不正确的结果"和"做你期望的事情"之间.你b->foo()
肯定是未定义的,因为它发生在你的b->~B()
电话之后,
由于你的foo
函数实际上没有使用被析构函数破坏的任何东西,所以调用foo
"工作",因为没有任何东西被破坏了.[这绝不是保证 - 它只是起作用,有点像有时在没有看的情况下过马路是好的,有时它不是.取决于它是什么道路,它可能是一个非常糟糕的主意,或者可能在大多数时间都可以工作 - 但是有一个原因让人们说"向左看,向右看,向左看,然后在安全的情况下交叉"(或类似的东西)那)]
调用delete
已经被破坏的对象也是UB,所以再次,它是"运作"的纯粹运气(在"不会导致程序崩溃"的意义上).
delete
与new []
UB 混合或反之亦然 - 再次,编译器[及其相关的运行时]可能会做正确或错误的事情,具体取决于环境和条件.
不要依赖程序中未定义的行为[1].它肯定会回来咬你.C和C++有相当多的UB案例,至少可以理解最常见的案例,例如"破坏后使用","免费使用"等等,并注意这些案例 - 并避免它不惜一切代价!