精华内容
下载资源
问答
  • 虚析构函数

    2019-12-08 16:12:34
    1、什么是虚析构函数? 将一个类的虚函数定义成虚函数,这个析构函数就叫虚析构函数。 注意:不允许以虚函数作为构造函数 2、为什么提倡把带有虚函数的类的析构函数写为虚析构函数 通过基类的指针删除派生类对象...

    1、什么是虚析构函数?

    将一个类的虚函数定义成虚函数,这个析构函数就叫虚析构函数。
    注意:不允许以虚函数作为构造函数

    2、为什么提倡把带有虚函数的类的析构函数写为虚析构函数

    通过基类的指针删除派生类对象时,通常情况下只调用基类的析构函数。但是,删除一个派生类的对象时,应该先调用派生类的析构函数,然后调用基类的析构函数。因此,我们可以引入虚析构函数解决这个问题。即:将基类的西沟函数生命为虚函数。

    3、基类、派生类虚析构函数的特点

    • 一般来说,一个类如果定义了虚函数,则应该将析构函数也定义成 虚函数。
    • 或者,一个类打算作为基类使用,也应该将析构函数定义 成虚函数。
    • 派生类的析构函数可以virtual不进行声明

    3、虚析构函数的执行过程

    通过基类的指针删除派生类对象时:

    • 首先调用派生类的析构函数
    • 然后调用基类的析构函数

    4、举栗子

    //版本一:不定义虚析构函数
    class son{
    public:
    	~son() {cout<<"bye from son"<<endl;};
    };
    class grandson:public son{
    public:
    	~grandson(){cout<<"bye from grandson"<<endl;};
    };
    int main(){
    	son *pson;
    	pson=new grandson();
    	delete pson;
    	return 0;
    }
    //输出: bye from son 没有执行grandson::~grandson()!!!
    
    //版本二:定义虚析构函数
    class son{
    public:
    	virtual ~son() {cout<<"bye from son"<<endl;};
    };
    
    class grandson:public son{
    public:
    	~grandson(){cout<<"bye from grandson"<<endl;};
    };
    int main() {
    	son *pson;
    	pson= new grandson();
    	delete pson;
    	return 0;
    }
    /*
    输出: bye from grandson
    bye from son
    执行grandson::~grandson(),引起执行son::~son()!!!
    */
    
    展开全文
  •  虚析构函数  析构函数的工作方式是:底层的派生类(most derived class)的析构函数先被调用,然后调用每一个基类的析构函数。  因为在C++中,当一个派生类对象通过使用一个基类指针删除,而这个基类有一个非...
  • 这篇文章用于总结当析构函数是普通析构函数、虚析构函数、纯虚析构函数时,我们使用delete运算符删除一个指针对象时,析构函数会有什么情况发生; 普通析构函数 CBase是基类,CDerive是其子类,类源码代码如下: ...

    我们知道对象在结束其生命周期之前,都会调用析构函数以完成必要的清理工作;派生类调用的析构函数顺序是“先子类,后基类”; 这篇文章用于总结当析构函数是普通析构函数、虚析构函数、纯虚析构函数时,我们使用delete运算符删除一个指针对象时,析构函数会有什么情况发生;

    普通析构函数

    CBase是基类,CDerive是其子类,类源码代码如下:

    class CBase
    {
    public:
        CBase(){}
        //基类析构函数
        ~CBase(){ cout << "CBase Destructor" << endl; } 
    private:
        int a;
    };
    
    class CDerive:public CBase
    {
    public:
        CDerive(){}   
        //子类析构函数
        ~CDerive(){ cout << "CDerive Destructor" << endl; }
    private:
        int b;
    };
    

    测试代码如下:

    //case1
    CDerive *pDeriveObj = new CDerive();
    delete pDeriveObj;
    
     //case2
    //基类指针对象可以指向派生类,体现基类和派生类赋值兼容关系(不同类型可以转化和赋值),
    //但是pBaseObj只能访问基类成员,不能访问派生类成员
    CBase* pBaseObj = new CDerive();
    delete pBaseObj; //等价于删除基类对象
    

    测试结果:

    /* case 1
    先析构子类:CDerive Destructor
    后析构基类:CBase Destructor
    /
    /
    case2
    仅析构基类:CBase Destructor
    */

    总结:

    1. 若delete运算符删除的是子类指针对象,则会调用子类和基类的析构函数;
    2. 若析构函数是非虚的,即使基类指针指向的是子类对象,则delete 指针对象时,也仅调用基类的析构函数;

    虚析构函数

    当基类中的析构函数设置为虚函数时,我们在delete 基类指针对象时,能根据实际类型完成对象的清理工作;源码如下:

    class CBase
    {
    public:
        CBase(){}
        //基类虚析构函数
        virtual ~CBase(){ cout << "CBase Destructor" << endl; } 
    private:
        int a;
    };
    
    class CDerive:public CBase
    {
    public:
        CDerive(){}   
        //子类虚析构函数
        virtual ~CDerive(){ cout << "CDerive Destructor" << endl; }
    private:
        int b;
    };
    

    测试代码:

    //指向基类对象
    CBase*pBaseObj_case1 = new CBase();
    delete pBaseObj_case1;
    
    //指向子类对象
    CBase*pBaseObj_case2 = new CDerive();
    delete pBaseObj_case2;
    

    运行结果:

    //case1
    CBase Destructor
    //case2
    CDerive Destructor ->先子类
    CBase Destructor ->后基类

    总结:
    当基类的析构函数为虚函数时,基类指针指向的是子类对象时,使用delete运算符删除指针对象,析构函能够按照“先子类,后基类”的原则完成对象清理;这样在多重继承的类中,能够保证每个类都能够得到正确的清理;比如基类和子类的缓冲区都能被释放;

    纯虚析构函数

    当基类中有纯虚函数时,基类是不能被实例化的,需要在子类中重写该纯虚函数;对于纯虚析构函数有点特殊,源码如下:

    class CBase
    {
    public:
        CBase(){}
        //基类析构函数
        virtual ~CBase()= 0;
    private:
        int a;
    };
    
    class CDerive:public CBase
    {
    public:
        CDerive(){}   
        //子类析构函数
        virtual ~CDerive(){ cout << "CDerive Destructor" << endl; }
    private:
        int b;
    };
    

    测试代码:

    CBase*pBaseObj = new CDerive();
    delete pBaseObj;
    

    当我们编译代码时,会发现代码编译不过提示:
    “error LNK2019: 无法解析的外部符号 “public: virtual __thiscall CBase::~CBase(void)” (??1CBase@@UAE@XZ),该符号在函数 “public: virtual __thiscall CDerive::~CDerive(void)” (??1CDerive@@UAE@XZ) 中被引用”

    原因是子类析构时需要调用基类的析构函数,但发现代码中没有实现CBase析构函数,导致编译异常;
    解决办法就是我们在CBase类外实现其函数体,而不是在子类中重写,而且~CDerive函数也是虚函数,即使其函数名不同;这就是和其他纯虚函数有特殊的地方;

    我们需要在CBase类外增加如下的析构函数:

    CBase::~CBase()
    { 
        cout << "CBase Destructor" << endl; 
    } 
    

    这样得到的运行结果是:

    CDerive Destructor ->先子类
    CBase Destructor ->后基类

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

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

    参考资料:
    https://www.cnblogs.com/jinxiang1224/p/8468244.html
    http://c.biancheng.net/cpp/biancheng/view/247.html
    http://blog.csdn.net/yapian8/article/details/46418687

    展开全文
  • 析构函数和虚析构函数析构函数虚析构函数 析构函数 析构函数与构造函数对应,当对象结束其生命周期,系统会自动执行析构函数。它不能带任何参数,也没有返回值(包括void类型)。只能有一个析构函数,不能重载。 ...

    析构函数和虚析构函数

    析构函数

    析构函数与构造函数对应,当对象结束其生命周期,系统会自动执行析构函数。它不能带任何参数,也没有返回值(包括void类型)。只能有一个析构函数,不能重载。

    如果一个类中有指针,且在使用的过程中动态的申请了内存,那么最好显示构造析构函数在销毁类之前,释放掉申请的内存空间,避免内存泄漏。

    析构函数的执行顺序:1)派生类本身的析构函数体;2)对象成员析构函数;3)基类析构函数。

    虚析构函数

    如果一个类的析构函数时虚函数,那么由它继承而来的所有子类的析构函数也是虚函数。析构函数被定义为虚函数之后,在使用指针引用时可以动态绑定,实现运行时的多态,保证使用基类指针就能够调用适当的虚构函数针对不同的对象进行清理工作。因此,对于有可能会被继承的父类的析构函数应设置为虚函数,我们使用基类指针指向该子类对象,释放基类指针时可以释放掉子类的空间,防止内存泄漏。

    class A{
    public:
    	~A(){
    		cout << "A destructor" << endl;
    	}
    };
    
    class B:public A{
    public:
    	B(){
    		p = new int(0);
    	}
    	~B(){
    		cout << "B destructor" << endl;
    		delete p;
    	}
    private:
    	int *p;
    };
    
    void fun(A *a){
    	delete a;
    }
    
    int main(){
    	A* b = new B();
    	fun(b);
    	return 0;
    }
    

    运行结果为:
    A destructor

    说明通过基类指针删除派生类对象时调用的是基类的析构函数,派生类的析构函数没有被执行,因此派生类对象中动态分配的内存没有得到释放,从而造成内存泄漏。

    避免上述错误的有效方法是将析构函数声明为虚函数,实现多态:

    class A{
    public:
    	virtual ~A(){
    		cout << "A destructor" << endl;
    	}
    };
    

    此时输出信息为:
    B destructor
    A destructor

    那么,为什么C++默认的析构函数不是虚函数
    C++默认的析构函数不是虚函数是因为虚函数需要额外的虚函数表和虚表指针,占用额外的内存。而对于不会被继承的类来说,其析构函数如果是虚函数,就会浪费内存。因此C++默认的析构函数不是虚函数,而是只有当需要当作父类时,设置为虚函数。

    展开全文
  • c++析构函数、虚析构函数、纯虚析构函数详解

    参考文章:c++析构函数、虚析构函数、纯虚析构函数详解

    补充:
    1. 纯虚函数
    是一种特殊的虚函数,它的一般格式如下:

    class <name>{
    	virtual<类型><函数名><参数列表> =0;
    };
    

    在许多情况下,在基类中不能对虚函数给出有意义的实现,而把它声明为纯虚函数,它的实现留给该基类的派生类去做。这就是纯虚函数的作用。

    纯虚函数可以让类先具有一个操作名称,而没有操作内容,让派生类在继承时再去具体地给出定义。凡是含有纯虚函数的类叫做抽象类。这种类不能声明对象,只是作为基类为派生类服务。除非在派生类中完全实现基类中所有的的纯虚函数,否则,派生类也变成了抽象类,不能实例化对象。

    一般而言纯虚函数的函数体是缺省的,但是也可以给出纯虚函数的函数体(此时纯虚函数仍然为纯虚函数,对应的类仍然为抽象类,还是不能实例化对象)调用纯虚函数的方法为:抽象类类名::纯虚函数名(实参表)

    2. 基类指针指向子类对象:
    没有指定虚函数, 那么它就只能访问到类型对应的函数
    基类指针就只能访问到基类函数
    子类指针就只能访问到子类函数
    定义了虚函数就可以访问到子类对应的函数。

    for example:

    //基类:
    class A {
    public:
        A();
        virtual ~A();
        void f1();
        virtual void f2();
        virtual void f3()=0;//纯虚函数
    };
    //子类:
    class B:public A{
    public:
        B();
        virtual ~B();
        void f1();
        virtual void f2();
        virtual void f3();
    };
    //主函数:
    int main(int argc,char * argv[]) {
        A *m_j = new B();
        m_j -> f1();//调用基类的函数
        m_j -> f2();//重写函数,调用的是子类的函数
        m_j -> f3();//虚函数,基类中的虚函数不用写具体的函数体。
        delete m_j;
        return 0;
    }
    

    3. 构造函数不能为虚函数
    1每个类都有一个虚函数表。虚函数对应一个指向vtable虚函数表的指针,如果构造函数是虚的,就需要通过 vtable来调用,可是对象还没有实例化,也就是内存空间还没有,不能找vtable。所以构造函数不能是虚函数。

    2虚函数主要用于在信息不全的情况下,能使重载的函数得到对应的调用。构造函数本身就是要初始化实例,那使用虚函数也没有实际意义呀。所以构造函数没有必要是虚函数。虚函数的作用在于通过父类的指针或者引用来调用它的时候能够变成调用子类的那个成员函数。而构造函数是在创建对象时自动调用的,不可能通过父类的指针或者引用去调用,因此也就规定构造函数不能是虚函数。

    3调用构造函数时还不能确定对象的真实类型(因为子类会调父类的构造函数);而且构造函数的作用是提供初始化,在对象生命期只执行一次,不是对象的动态行为,也没有必要成为虚函数。

    参考:
    构造函数为什么不能是虚函数

    1. 是不是可以把每个函数都声明为虚函数?
      不行,虚函数是有代价的。
      每个虚函数都必须维护一个虚函数表,使用虚函数的时候都会产生一个系统开销。
    展开全文
  • 1.为什么要定义虚析构函数?如果有一个带有虚函数功能的类,则它需要一个虚析构函数,原因如下:1)如果一个类有虚函数功能,它经常作为一个基类使用;2)如果它是一个基类,它的派生类经常使用new来分配;3)如果一个...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 5,347
精华内容 2,138
关键字:

虚析构函数