为多态基类声明virtual析构函数

本文探讨了在C++中为何基类需要声明virtual析构函数,特别是在涉及多态和派生类的情况下。当通过基类指针删除派生类对象时,若基类的析构函数不是virtual,则可能导致资源泄漏。文章提供了具体的代码示例和解决方案。

一、中心内容

1、polymorphic(带多态性质的)base classes应该声明一个virtual析构函数。如果class带有任何virtual函数,它就应该拥有一个virtual析构函数;

2、Classes的设计目的如果不是作为base Classes使用,或不是为了具备多态性,就不该声明virtual析构函数。

二、内容简介

1、资源泄漏

class TimeKeeper{

public:

    TimeKeeper();

    ~TimeKeeper();

    ......

};

class AtomicClock: public TimeKeeper{...};

class WaterClock: public TimeKeeper{...};

class WristWatch: public TimeKeeper{...};

int main

{

    TimeKeeper* ptk = getTimeKeeper();//getTimekeeper()返回一个指针,指向一个派生类的动态分配的指针

    ......

    delete ptk;

}

note:

1、问题出在基类的析构函数不是virtual的;

因为getTimeKeeper()返回的指针指向一个派生类对象,而那个对象却经由一个基类指针被删除,所以可能会造成对象的衍生部分没有被销毁,造成“局部销毁”的现象。

2、解决办法:给基类加上一个virtual析构函数

class TimeKeeper{

public:

    TimeKeeper();

   virtual  ~TimeKeeper();

    ......

};

任何class只要带有virtual函数几乎确定应该也有一个virtual析构函数


2、若class不含virtual函数,通常表示它并不意图被用作一个基类

    这时候不要令析构函数为virtual!!!

(1)一般不要继承不含任何virtual函数的类。比如:string,vector, list, set......

(2)抽象类

抽象类是由pure virtual析构函数导致的,它不能够实体化,也就是说不能为其创建对象。

所以当你希望有一个抽象类时,为其声明一个pure virtual析构函数。。

e.g

class AWOV{

public:

    virtual ~AWOV() = 0;//声明一个pure virtual析构函数

};

AWOV::~AWOV(){}//还得给此析构函数一个定义,不然连接器会出错


note:

析构函数的运作方式:

先调用最深层次的那个class的析构函数最先被调用,然后是其每一个基类class的析构函数被调用


<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、付费专栏及课程。

余额充值