多态基类的析构函数必须加上virtual

《Effective C++》说:

        因为C++明确指出,当derived class对象经由一个base class指针被删除,而base class带着一个non-virtual析构函数,其结果未有定义---实际执行时通常发生的是对象的derived成分没被销毁。如:baseclass *ptb = derivedclassFuc() //子类的一个函数; 其中baseclass中有个non-virtual析构函数。解决这一问题的方法就是把基类中的析构函数声明为virtual,通常还有其他virtual函数。任何class只要还有virtual函数都几乎确定应该也有一个virtual析构函数。                                     

       如果class不含virtual函数,通常表示它并不意图被用做一个base class.当class不企图被当作base class,令其析构函数为virtual往往是个馊主意。许多人的心得是:只有当class内含至少一个virtual函数,才为它声明virtual析构函数。

有时候令Class带一个pure virtual析构函数,可能颇为便利,纯虚函数导致抽象类,也就是不能被实体化的类,你不能为这种类型创建对象。所以为你希望它成为抽象的那个类声明一个pure virtual析构函数,这样你就不需要担心析构函数的问题了,不过你必须为这个pure virtual析构函数提供一份定义:如,AWOV::~AWOV ( ) { …….},析构函数的运作方式是,最深层派生的那个class其析构函数最先被调用,然后是其每一个base class的析构函数被调用。编译器会在AWOV的derived classes的析构函数中创建一个对~AWOV的调用动作,所以你必须为这个函数提供一份定义,否则,连接器会出问题。

=======================================
把析构函数定义成虚函数,可以有效的防止在之后继承的时候,子类指针的内存泄露.
比如,
class A
class B:public A
A *ptr=new B
在这种情况下调用析构函数的时候,就会发生删除了A的空间而没有删除B的空间,
所以将基类定义为抽象类,可以让其子类动态的去调用它自身的函数。

========================================

class Base
{
public:
    Base(){}
    virtual void VirFun()
    {
    }
     ~Base(){}    //实际上这里必须加virtual 
private:
    //Some attribute
};

class Sub : public Base
{
public:
    Sub(){}
    virtual void VirFun()
    {
    }
    ~Sub(){}
private:
    //Some attribute
};

上面的例子是个简单的多态。

在我们应用的时候,我们经常会这么干:

Base * pBase = new Sub();//new了一个Sub对象,它实际上包含了Base 的子对象。
pBase->VirFun(); //很好,它会根据实际的类型调用Sub 的 VirFun()
delete pBase;//完蛋了,它调用了Base的析构。Sub的析构?这可不关我事,我可是Base类型的。

于是内存泄漏了。。。

给Base的析构函数加上virtual之后呢?

delete pBase; //很好,它继续多态调用实际类型对象的析构。

大家知道,子类的析构函数会自动调用父类的析构。

于是,啊哈,终于安全了。

至此,大家应该对题目的意思有了大概的了解了。

有没有发现,virtual 析构函数挺有意思的,它的子类析构函数名字和父类不同,一样会重写父类的析构函数。

为什么题目要强调“多态 基类”呢?

class Base
{
public:
    Base(){}
    void Fun()
    {
    }
   

    virtual ~Base(){}
private:
    //Some attribute
};

class Sub : public Base
{
public:
    Sub(){}
    void Fun()
    {
    }


    ~Sub(){}
private:
    //Some attribute
};

这样的继承中,有没有必要给父类析构函数加virtual呢?

从语法意义上来说,是应该的,因为你像下面这样写的时候,语法是没有错误的:

Base * pBase = new Sub();//new了一个Sub对象,它实际上包含了Base 的子对象。
delete pBase; //很好啊,程序多安全啊。有什么问题吗?

在这样写的时候,你应该谴责自己。

pBase的所有行为都是Base中定义的,为什么要给它一个Sub的身体呢?

(为什么网上很多男士都爱用美女作头像呢。。。)

所以,当你设计的类系不具备多态性的时候,请不要:

1.用父类的指针指向子类;

2.给父类的析构函数加上virtual(毕竟虚函数表也是不小的开销不是?)

<think>嗯,用户现在想理解为什么C++中派生析构函数会自动调用基类析构函数。我之前已经回答过似的问题,关于基类析构函数为什么需要声明为虚函数,现在用户可能想更深入理解自动调用的机制。首先,我需要回顾一下之前的回答和相关引用。用户提供的引用中有几个关键点:引用[2]提到,当派生对象被删除,会先调用派生析构函数,然后自动调用基类的。引用[4]则解释了虚析构函数的设计目的,确保资源正确释放,编译器会自动安排调用顺序。而引用[3]中提到,是否需要显式调用基类析构函数的问题,指出如果基类有动态资源,是否需要显式调用,但根据标准,析构函数会自动调用基类的,所以可能用户有误解。 用户的问题核心是为什么派生析构函数会自动调用基类析构函数。需要从对象销毁机制、继承链的构造和析构顺序、编译器行为等方面来解释。首先,对象的构造顺序是从基类到派生,析构则是相反。这是因为派生可能依赖于基类成员的存在,所以在构造先构建基类部分。析构,先处理派生自己的资源,再清理基类,避免依赖问题。然后,析构函数的调用是编译器隐式插入的,当派生析构函数执行完毕后,编译器会自动调用基类析构函数。这不需要程序员显式调用,否则会导致重复释放的问题,尤其是当基类析构函数虚函数,保证正确的析构顺序。 另外,需要澄清引用[3]中的内容,用户可能误解了需要显式调用基类析构函数的情况。实际上,标准规定派生析构函数会自动调用基类析构函数,不需要手动调用。只有在特定情况下,比如使用placement new或者手动管理内存,可能需要显式调用,但一般情况下不需要。 接下来,需要结合引用中的例子,比如引用[5]中的代码示例,析构函数的输出顺序是B先于A,说明派生析构后自动调用基类。这进一步验证了自动调用的机制。 可能还需要解释虚析构函数的作用,即使基类析构函数是虚的,析构顺序仍然是派生到基类,但虚函数表确保了通过基类指针删除对象,能正确调用派生析构函数,从而触发整个链式调用。总结来说,自动调用基类析构函数的原因包括:对象销毁顺序的必然要求、编译器隐式插入代码、资源释放的完整性保证,以及继承体系的安全维护。需要将这些点分层次解释清楚,并引用相关的引用内容来支持论点,比如引用[2]和引用[4]中的内容。同,用户之前的问题涉及虚析构函数的重要性,现在的问题更偏向于自动调用的机制,所以需要区分这两者的关系。虚析构函数影响的是多态情况下析构函数能否被正确调用,而自动调用基类析构函数是无论析构函数是否为虚都会发生的,前提是派生析构函数被正确调用。最后,生成相关问题,可能需要包括构造和析构顺序、虚析构函数的影响、显式调用析构函数的情况等,帮助用户进一步学习。</think>### C++派生析构函数自动调用基类析构函数的原因 #### 1. **对象销毁顺序的必然要求** C++中对象的构造和析构遵循严格的**栈式顺序**: - **构造顺序**:基类 $\rightarrow$ 派生(派生可能依赖基类成员的存在)[^1] - **析构顺序**:派生 $\rightarrow$ 基类(派生资源应先释放,避免基类资源被破坏后导致派生资源释放失败)[^2][^5] 示例: ```cpp class Base { /* ... */ }; class Derived : public Base { /* ... */ }; Derived d; // 构造顺序:Base() → Derived() // 析构顺序:~Derived() → ~Base() ``` #### 2. **编译器隐式插入基类析构调用** 当派生析构函数执行完毕后,编译器会自动插入对基类析构函数的调用代码[^4]。 - **无需手动调用**:若显式调用基类析构函数(如`Base::~Base()`),会导致重复释放资源,引发未定义行为[^3]。 - **虚析构函数的影响**:若基类析构函数声明为`virtual`,通过基类指针删除派生对象虚函数表(vtable)会确保调用完整的析构链(派生析构函数 $\rightarrow$ 基类析构函数)[^4]。 #### 3. **资源释放的完整性保证** - **派生析构函数**:负责释放派生独有的资源(如动态内存、文件句柄)。 - **基类析构函数**:负责释放基类自身的资源。 若未自动调用基类析构函数基类资源会泄漏,导致内存泄漏或程序崩溃[^2][^3]。 #### 4. **继承体系的安全维护** - **自动调用机制**:确保无论派生是否显式定义析构函数基类析构函数总能被调用。 - **避免程序员错误**:防止因忘记调用基类析构函数而引入资源泄漏风险[^4]。 --- #### 示例分析 引用[5]中的代码: ```cpp class A { /* ... */ ~A() { ... } }; class B : public A { /* ... */ ~B() { ... } }; int main() { B x(5), y(6, 7); // 对象销毁输出顺序: // ~B() → ~A() // ~B() → ~A() } ``` 输出结果为: ``` Destructor B!0 Destructor A!5 Destructor B!0 Destructor A!6 ``` 验证了派生析构函数执行后自动调用基类析构函数[^5]。 --- ### 关键总结 | 特性 | 构造函数 | 析构函数 | |---------------------|---------------|------------------| | 调用顺序 | 基类→派生 | 派生基类 | | 是否需显式调用基类 | 显式/隐式指定 | 编译器自动插入 | | 多态场景下的行为 | 无影响 | 依赖虚析构函数 | --- ### 相关问题 1. **如何避免基类析构函数未被调用导致的资源泄漏?** 2. **虚析构函数如何影响多态对象的销毁过程?** 3. **为什么C++不允许手动调用基类析构函数?** 4. **构造函数和析构函数的调用顺序是否受多重继承影响?** 5. **如何验证编译器是否自动插入基类析构函数调用?** [^1]: 构造函数顺序与显式/隐式调用 [^2]: 派生析构后自动调用基类析构函数 [^3]: 显式调用基类析构函数的风险 [^4]: 虚析构函数多态销毁机制 [^5]: 析构顺序的代码验证
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值