You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++技术问题:析构函数为何在退出作用域前被调用?

为什么A a = C();会产生额外的析构调用?

问题代码

#include <iostream>

using namespace std;

class A {
    public:
    A() {cout << "A constructor\n";}
    ~A() {cout << "A deconstructor\n";}
};

class B : public A {
    public:
    B() {cout << "B constructor\n";}
    ~B() {cout << "B deconstructor\n";}
};

class C : public B {
    public:
    C() {cout << "C constructor\n";}
    ~C() {cout << "C deconstructor\n";}
};

int main() {
    A a = C();
    cout << "Exiting scope of main!" << endl;
}

实际输出

A constructor
B constructor
C constructor
C deconstructor
B deconstructor
A deconstructor
Exiting scope of main!
A deconstructor

对比指针版本的输出

若将main第一行替换为A* a = new C();,输出为:

A constructor
B constructor
C constructor
Exiting scope of main!


原因分析

1. A a = C();的执行流程

这行代码是拷贝初始化,完整执行步骤如下:

  • 创建临时C对象:按照继承顺序,依次调用A→B→C的构造函数,对应输出前3行构造日志。
  • 临时对象初始化a:这里发生对象切片——a是A类型,只会拷贝临时C对象中属于A的部分,B、C的专属成员被丢弃。编译器会生成默认拷贝构造函数(无输出)完成a的初始化。
  • 临时对象销毁:临时C对象的生命周期在当前表达式结束后立刻结束,按C→B→A的顺序调用析构函数,对应中间3行析构日志。
  • 执行cout语句输出提示信息。
  • main函数结束:局部变量a(A类型)销毁,调用A的析构函数,对应最后一行日志。

2. A* a = new C();的执行流程

这行代码是创建堆上C对象并让基类指针指向它:

  • new C()创建堆上C对象,按A→B→C顺序调用构造函数,对应前3行输出。
  • 指针a仅保存C对象的地址,不会触发对象切片(对象本身仍是完整的C类型)。
  • 由于未编写delete a;,堆上的C对象不会在main结束前销毁(程序退出时由操作系统回收内存),因此退出main前无析构调用。

关键知识点

  • 对象切片:当用派生类对象直接初始化/赋值给基类对象时,基类对象仅保留派生类中的基类部分,派生类专属成员被丢弃;若涉及临时对象,临时对象会在表达式结束后立即销毁。
  • 析构多态性:若A的析构函数不是虚函数,即使基类指针指向派生类对象,调用delete时也只会触发基类的析构函数(本例指针版本未写delete,故未体现)。

内容的提问来源于stack exchange,提问作者vasker

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 15:55:20