虚析构函数(删除基类对象时,先调用派生类的虚构函数,再调用基类虚析构函数)

本文详细介绍了C++中虚析构函数的作用及其使用场景。通过实例对比,阐述了虚析构函数如何确保派生类的析构函数被正确调用,避免资源泄露问题。同时强调了将其设置为虚函数的重要性。

我们已经介绍过析构函数(详情请查看:C++析构函数),它的作用是在对象撤销之前做必要的“清理现场”的工作。

当派生类的对象从内存中撤销时一般先调用派生类的析构函数,然后再调用基类的析构函数。

1、系统会只执行基类的析构函数,而不执行派生类的析构函数。如下例子
[例12.3] 基类中有非虚析构函数时的执行情况。为简化程序,只列出最必要的部分。

  1. #include <iostream>
  2. using namespace std;
  3. class Point  //定义基类Point类
  4. {
  5. public:
  6.    Point( ){}  //Point类构造函数
  7.    ~Point(){cout<<"executing Point destructor"<<endl;}  //Point类析构函数
  8. };
  9. class Circle:public Point  //定义派生类Circle类
  10. {
  11. public:
  12.    Circle( ){}  //Circle类构造函数
  13.    ~Circle( ){cout<<"executing Circle destructor"<<endl;}  //Circle类析构函数
  14. private:
  15.    int radius;
  16. };
  17. int main( )
  18. {
  19.    Point *p=new Circle;  //用new开辟动态存储空间
  20.    delete p;  //用delete释放动态存储空间
  21.    return 0;
  22. }

这只是一个示意的程序。p是指向基类的指针变量,指向new开辟的动态存储空间,希望用detele释放p所指向的空间。但运行结果为:

executing Point destructor

表示只执行了基类Point的析构函数,而没有执行派生类Circle的析构函数。

2、如果希望能执行派生类Circle的析构函数,可以将基类的析构函数声明为虚析构函数,
如:

    virtual ~Point(){cout<<″executing Point destructor″<<endl;}

程序其他部分不改动,再运行程序,结果为:

executing Circle destructor
executing Point destructor

先调用了派生类的析构函数,再调用了基类的析构函数,符合人们的愿望。

当基类的析构函数为虚函数时,无论指针指的是同一类族中的哪一个类对象,系统会采用动态关联,调用相应的析构函数,对该对象进行清理工作。

如果将基类的析构函数声明为虚函数时,由该基类所派生的所有派生类的析构函数也都自动成为虚函数,即使派生类的析构函数与基类的析构函数名字不相同。

最好把基类的析构函数声明为虚函数。这将使所有派生类的析构函数自动成为虚函数。这样,如果程序中显式地用了delete运算符准备删除一个对象,而delete运算符的操作对象用了指向派生类对象的基类指针,则系统会调用相应类的析构函数。

3、虚析构函数的概念和用法很简单,但它在面向对象程序设计中却是很重要的技巧。

专业人员一般都习惯声明虚析构函数,即使基类并不需要析构函数,也显式地定义一个函数体为空的虚析构函数,以保证在撤销动态分配空间时能得到正确的处理。

构造函数不能声明为虚函数。这是因为在执行构造函数时类对象还未完成建立过程,当然谈不上函数与类对象的绑定。

<think>###为什么基类需要虚析构函数才能正确删除派生类对象?**回答:**基类需要虚析构函数来确保当通过基类指针删除派生类对象,能够正确调用派生类析构函数。如果基类析构函数不是虚函数,则只会调用基类析构函数,导致派生类资源泄漏。####详细解释**1.虚析构函数的问题**当基类析构函数不是虚函数,通过基类指针删除派生类对象会引发**未定义行为**(通常是派生类部分未被销毁):```cppclassBase{public:~Base(){cout<<"~Base()\n";}//非虚析构};classDerived:publicBase{public:Derived(){data=newint[100];}//分配资源~Derived(){delete[]data;//释放资源cout<<"~Derived()\n";}private:int*data;};intmain(){Base*obj=newDerived();deleteobj;//仅调用Base::~Base(),导致内存泄漏}```***输出**:仅`~Base()`***后果**:`Derived`的`data`数组未被释放(内存泄漏),`~Derived()`未被调用。**2.虚析构函数的解决方案**将基类析构函数声明为虚函数后:```cppclassBase{public:virtual~Base(){cout<<"~Base()\n";}//虚析构};//Derived类定义不变...intmain(){Base*obj=newDerived();deleteobj;//正确调用Derived::~Derived()->Base::~Base()}```***输出**:```~Derived()~Base()```***调用链**:`deleteobj`→调用虚析构函数→根据动态类型`Derived`调用`Derived::~Derived()`→自动调用基类析构函数`Base::~Base()`####技术原理**虚析构函数的动态绑定机制**-**虚函数表(vtable)**:当类有虚函数,编译器为其生成虚函数表,其中包含所有虚函数地址(包括析构函数)。-**析构函数调用过程**:1.通过基类指针调用`delete`,查找虚函数表获得实际析构函数地址。2.调用派生类析构函数(释放派生类资源)。3.派生类析构函数执行完毕后,自动调用基类析构函数(通过静态绑定)。```mermaidgraphTDA[deletebase_ptr]-->B{基类析构是否虚函数?}B-->|是|C[查虚表调用实际析构函数]C-->D[执行派生类析构函数]D-->E[自动调用基类析构函数]B-->|否|F[直接调用基类析构函数]F-->G[派生类析构未执行→资源泄漏]```####最佳实践1.**基类设计规则**-如果类可能被继承(作为基类),**必须声明虚析构函数**。-即使基类不需要析构逻辑,也应提供空实现的虚析构函数:```cppclassAbstractBase{public:virtual~AbstractBase()=default;//C++11风格};```2.**最终类(禁止继承)的优化**若类不应被继承(C++11起可用`final`关键字),可不使用虚析构函数以避免虚表开销:```cppclassFinalClassfinal{//禁止继承public:~FinalClass(){...}//非虚析构};```3.**抽象类的纯虚析构函数**纯虚析构函数仍需提供实现(因为派生类析构会隐式调用它):```cppclassAbstractBase{public:virtual~AbstractBase()=0;//纯虚声明};//必须提供实现(在类外)AbstractBase::~AbstractBase(){}//空实现```####常见错误场景-**STL容器存储基类指针**:```cppstd::vector<Base*>vec;vec.push_back(newDerived());//若~Base()非虚,delete发生资源泄漏```-**智能指针使用**:```cppstd::unique_ptr<Base>p=std::make_unique<Derived>();//若~Base()非虚,同样导致派生类析构未被调用```---§§相关问题§§:1.虚析构函数的实现原理是什么?2.虚析构函数为什么需要提供实现?3.使用`final`关键字修饰的类是否还需要虚析构函数?4.在多继承场景下,虚析构函数如何工作?</think>### 为什么基类需要虚析构函数才能正确删除派生类对象? **答案:** 基类需要虚析构函数是为了确保当通过基类指针删除派生类对象,能够正确调用完整的析构函数链(包括派生类析构函数)。如果基类析构函数不是虚函数,则只会调用基类析构函数,导致派生类资源泄漏。 #### 详细解释 ##### 1.虚析构函数的问题 当基类析构函数不是虚函数,通过基类指针删除派生类对象会导致**派生类析构函数不被调用**: ```cpp #include <iostream> using namespace std; class Base { public: ~Base() { cout << "Base destructor\n"; } // 非虚析构函数 }; class Derived : public Base { public: ~Derived() { cout << "Derived destructor\n"; } // 不会被调用 }; int main() { Base* obj = new Derived(); delete obj; // 仅调用 Base::~Base() return 0; } ``` **输出结果:** ``` Base destructor ``` **问题:** - `Derived` 的析构函数未被调用 - 如果 `Derived` 持有资源(如动态内存、文件句柄等),会导致资源泄漏 ##### 2. 虚析构函数的解决方案 将基类析构函数声明为虚函数后: ```cpp class Base { public: virtual ~Base() { cout << "Base destructor\n"; } // 虚析构函数 }; // Derived 类保持不变... int main() { Base* obj = new Derived(); delete obj; // 正确调用派生类析构函数 return 0; } ``` **输出结果:** ``` Derived destructor Base destructor ``` **关键机制:** 1. **动态绑定**:虚析构函数通过虚函数表(vtable)实现动态绑定 2. **调用顺序**: - 调用派生类析构函数 - 自动调用基类析构函数(通过静态绑定) 3. **内存释放**: ```mermaid graph LR A[delete base_ptr] --> B[查虚表找实际析构函数] B --> C[调用Derived::~Derived] C --> D[执行派生类资源释放] D --> E[自动调用Base::~Base] E --> F[执行基类资源释放] ``` #### 技术原理深度解析 1. **对象内存布局**: ```cpp Derived object memory: [vptr] -> Derived vtable [Base members] [Derived members] ``` - `vptr` 指向包含析构函数地址的虚表 2. **虚表结构**: ``` Derived vtable: [0] Destructor address: Derived::~Derived [1] Other virtual functions... ``` 3. **删除操作的本质**: ```cpp // delete obj 的等价操作 if (obj != nullptr) { // 1. 通过vptr查找析构函数 void (*dtor)(void*) = obj->vptr[0]; // 2. 调用实际析构函数 dtor(obj); // 调用Derived::~Derived // 3. 释放内存 operator delete(obj); } ``` #### 关键应用场景 1. **多态对象处理**: ```cpp class Shape { public: virtual ~Shape() = default; // 必须为虚析构 virtual void draw() const = 0; }; class Circle : public Shape { // 资源管理 vector<double> vertexData; public: void draw() const override { /*...*/ } ~Circle() { /* 释放vertexData资源 */ } }; int main() { Shape* shape = new Circle(); // ... 使用shape ... delete shape; // 必须调用Circle::~Circle } ``` 2. **智能指针的正确使用**: ```cpp std::unique_ptr<Base> ptr = std::make_unique<Derived>(); // 当ptr离开作用域: // 如果~Base()非虚 → 仅调用Base析构函数 // 如果~Base()为虚 → 正确调用Derived和Base析构函数 ``` #### 特殊注意事项 1. **抽象类的纯虚析构函数**: ```cpp class AbstractBase { public: virtual ~AbstractBase() = 0; // 纯虚声明 }; // 必须提供实现(派生类析构会隐式调用它) AbstractBase::~AbstractBase() {} ``` 2. **继承体系中的层级处理**: - 只要最顶层基类虚析构函数,所有派生类都会受益 - 中间层的虚析构函数声明是好的实践但不是绝对必要 3. **性能考量**: - 虚析构函数会增加虚表指针(vptr)开销(每个对象8字节) - 在性能敏感场景,对不会被继承的类使用`final`关键字避免虚函数开销 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值