虚函数/构造函数/析构函数

本文探讨了C++中构造函数不能设为虚函数的原因及析构函数设为虚函数的重要性,通过实例演示了不同情况下析构函数的行为,强调了在类继承中正确使用虚析构函数以避免内存泄漏。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、构造不设为虚函数的原因:

虚函数:意思就是开启动态绑定,程序会根据对象的动态类型来选择要调用的方法;

在构造函数运行的时候对象的动态类型还不完整,没办法确定是什么类型,所以构造函数不可以进行动态绑定;

动态绑定是根据对象的动态类型而不是函数名,在调用构造函数之前,这个对象根本不存在,又何从谈起动态绑定??

2、析构函数设为虚函数的作用:

在类的继承中,如果有指针指向派生类,那么用基类指针delete的时候,如果不定义成虚函数的话,派生类中派生的

那部分无法析构;

#include<iostream>
using namespace std;
class base1{
public:
~base1(){}
};
class derived1:public base1{
public:
~derived1(){};
};
class base2{
public:
virtual ~base2(){};
};
class derived2:public base2{
public:
~derived2(){};
};
int main(){
base1* bp=new derived1;//upcast 父类的指针new一个子类的对象
delete bp;
base2* b2p=new derived2;//upcast 同上
delete b2p;
}///:~

当运行上面的程序的时候,将会看到,delete bp只调用基类的析构函数,当delete b2p的时候,在基类的析构函数

执行之后,派生类的析构函数将会执行,正是我们所希望的;

不把析构函数设为一个虚函数是一个隐匿的错误,因为它常常不会对程序有直接的影响,但是不知不觉中会造成泄露;

***************************************************************************************

为什么构造函数不可以用虚函数:

class A {
public:
  A() { a = "A"; f(); }
  virtual ~A() {}

  virtual f() { cout << a << endl; }
private:
  char* a;
};
class B : public A {
public:
  B() { b = "B"; }
  ~B() {}

  f() { cout << b << endl; }
private:
  char* b;
};

构造函数调用顺序是先调用基类构造函数:

A(),B();

在A()中调用f(),如果按照虚函数调用规则,他应该调用B::f(),但是此时B还没有构造(B()还没有执行),执行B::f()必然是一个错误;

所以在构造函数内部执行的时候,对象还没有完全构造好,不能按照虚函数方式调用,所以在构造函数内部执行的时候,对象还没有完全构造好,不能按照虚函数方式

进行调用,即是构造函数本身不可以是虚函数的原因;

符:构造函数不能声明为虚函数的原因:

1、构造一个对象的时候,必须知道对象的类型,而虚函数行为是在运行期间确定实际类型的,构造一个对象的时候

由于对象还未构造成功,编译器无法知道对象的实际类型,是该类本身还是该类的一个派生类或者更深层次的派生类;

2、虚函数的执行依赖于虚函数表,而虚函数表在构造函数中进行初始化工作,即初始化VPTR,让他指向正确的虚函数表,

在构造对象期间,虚函数表还没有被初始化,将无法执行,也就无从谈起执行构造函数这个虚函数了;

*******************************************************************************************************************************************

几种情况的介绍

1)析构函数不是虚函数:

class student     
{     
public:     
    int *m_pInt;     
    student()     //基类构造函数
    {     
        m_pInt = new int[10];   //     
        memset(m_pInt, 0, 10*4);     
    }     
     
    ~student()     //基类析构函数
    {               //   
        delete []m_pInt;     
    }     
     
};     
     
class GradeOneStue:public student     
{     
public:     
    int m_iNum;     
    GradeOneStue()         //派生类构造函数      
    {               //2     
        m_iNum = 1;     
    }     
     
    ~GradeOneStue()     //派生类析构函数
    {               //4     
        m_iNum = 0;     
    }     
};     
     
int _tmain(int argc, _TCHAR* argv[])     
{     
    GradeOneStue gd;     
    return 0;     
} 
main函数里定义一个派生类对象gd,构造顺序:先调用父类的构造函数,再调用子类构造函数;析构顺序:先调用子类的析构函数,再调用父类的析构函数;
2)析构函数是虚函数:

即使是虚函数,析构函数最后还是调用了父类的析构函数;

3)用父类指针new一个子类对象,析构函数不是虚函数:

只调用父类的析构函数,此时子类的析构函数没有被调用,此时子类的析构函数中虽然有调用父类析构函数的代码,但是这里直接调用

的是父类的析构函数,所以这时如果子类中析构函数有释放资源的代码将会造成这部分的资源不被释放,内存泄漏;

4)用父类的指针new一个子类对象,析构函数是虚函数:

由于子类的析构函数最后会调用父类的析构函数(不管是否是virtual,子类析构函数最后都会调用父类的析构函数),所以

最终父类的析构函数被执行,而由于构造的时候用的是子类类型去new,所以虚函数表中的析构函数地址是子类的析构

函数地址,也即是会首先调用子类析构函数,不会有内存泄漏


总结:

1、构造函数不能声明为虚函数,析构函数可以声明为虚函数,而且又是必须声明为虚函数,不建议在构造函数和析构函数

里面调用虚函数;

2、无论父类的与子类的析构函数是否是virtual,子类的析构函数都会调用父类的析构函数;

3、如果父类的与子类的析构函数不是virtual,用一个父类的指针指向一个用子类类型new的对象,delete的时候,直接调用

父类的析构函数,这个在编译时刻就决定的,如果,子类的析构函数中有释放资源的代码,会发生内存泄漏;

4、如果父类与子类的析构函数是virtual,用一个父类的指针指向一个用子类类型new的对象,delete的时候,由于4

是通过虚函数表调用析构函数,而虚函数表中的地址是构造的时候写入的,是子类的析构函数的地址,所以子类的和父类的

析构函数都会被调用,不会发生资源泄漏;
























评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值