c++中多态调用场景下基类析构函数的virtual声明

文章目录

  • 一.基类析构函数未加virtual声明的情况
    • 1.1 基础示例演示
    • 1.2 进阶示例演示
  • 二.基类析构函数添加virtual声明的情况
  • 三.总结

一.基类析构函数未加virtual声明的情况

  在多态场景中,可通过基类的指针指向子类对象,并完成对子类对象的成员函数调用;在函数对象析构时,根据继承顺序,往往是先调用子类的析构函数,然后调用基类的析构函数,但是在多态场景中,可能会出现以下错误的情况:

1.1 基础示例演示

#include 

using namespace std;
 
class base
{
public:
    base(){cout<<"基类构造"<<endl;};
    ~base(){cout<<"基类析构"<<endl;};
};
 
class obj:public base
{
public:
    obj(){cout<<"子类构造"<<endl;};
    ~obj(){cout<<"子类析构"<<endl;};
};

int main()
{
    base *pbase=new obj;    // 创建一个基类指针,并指向子类对象
    delete pbase;           // 析构基类对象
    pbase=nullptr;

    return 0;
}

  上述程序看似没问题,但是执行下来发现,输出结果如下:
c++中多态调用场景下基类析构函数的virtual声明_第1张图片
  可以发现此处在析构时仅仅调用了基类的析构函数,并没有调用子类的析构函数,在上述例子中并不会造成什么太坏影响,但是如果在子类的析构函数中做了内存释放、资源清楚等事情,上述情况就会造成内存泄漏,具体见下面的示例。

1.2 进阶示例演示

#include 
#include 

using namespace std;
 
class base
{
public:
    base(){cout<<"基类构造"<<endl;};
    ~base(){cout<<"基类析构"<<endl;};
    virtual void display(void) = 0;
};
 
class obj:public base
{
public:
    obj(){
        p = (char *)malloc(100);
        strcpy(p,"子类申请的内存空间调用");
        cout<<"子类构造"<<endl;
    };
    ~obj(){
        free(p);
        p=nullptr;
        cout<<"子类析构"<<endl;
    };
    virtual void display(void)
    {
        std::cout<<string(p)<<std::endl;
    };
private:
    char *p =nullptr;
};

int main()
{
    base *pbase=new obj;    // 创建一个基类指针,并指向子类对象
    pbase->display();       // 基类指针调用子类对象,多态
    delete pbase;           // 析构基类对象
    pbase=NULL;
    return 0;
}

程序执行结果:
c++中多态调用场景下基类析构函数的virtual声明_第2张图片
  在上述示例中,可以发现在子类的构造函数中申请了100 byte的内存空间给指针p,在析构函数中释放了该内存,因此在执行子类构造函数后必须执行其析构函数,否则会造成内存泄漏,上述的执行结果显然没有调用子类的析构函数,必然造成p申请的内存空间得不到释放,造成内存泄漏,如何解决?基类析构函数需要加virtual声明。具体见下。

二.基类析构函数添加virtual声明的情况

  如何解决在多态调用的场景中确保子类的对象能够被及时析构------在基类的析构函数加virtual声明。

#include 
#include 

using namespace std;
 
class base
{
public:
    base(){cout<<"基类构造"<<endl;};
    virtual ~base(){cout<<"基类析构"<<endl;};    // !!! virtual声明很关键
    virtual void display(void) = 0;    
};
 
class obj:public base
{
public:
    obj(){
        p = (char *)malloc(100);
        strcpy(p,"子类申请的内存空间调用");
        cout<<"子类构造"<<endl;
    };
    ~obj(){
        free(p);
        p=nullptr;
        cout<<"子类析构"<<endl;
    };
    virtual void display(void)
    {
        std::cout<<string(p)<<std::endl;
        // std::cout<<"dasdasdsa"<
    };
private:
    char *p =nullptr;
};

int main()
{
    base *pbase=new obj;    // 创建一个基类指针,并指向子类对象
    pbase->display();       // 基类指针调用子类对象,多态
    delete pbase;           // 析构基类对象
    pbase=NULL;
    return 0;
}

程序执行结果:
c++中多态调用场景下基类析构函数的virtual声明_第3张图片

  上述示例中,在基类的析构函数声明时添加了virtual关键字,可见执行结果中调用了子类的析构函数,自然在子类析构函数中释放了p申请的内存,避免了内存泄漏。

三.总结

(1)基类的的析构函数不是虚函数的话,删除指针时,只有基类的内存被释放,派生类的没有,这样就内存泄漏了。

(2)析构函数不是虚函数的话,直接按指针类型调用该类型的析构函数代码,因为指针类型是基类,所以直接调用基类析构函数代码。

(3)当基类指针指向派生类的时候,如果析构函数不声明为虚函数,在析构的时候,不会调用派生类的析构函数,从而导致内存泄露。

(4)子类对象创建时先调用基类构造函数然后在调用子类构造函数,在清除对象时顺序相反,所以delete pbase只清除了基类,而子类没有清除。

(5) 什么时候才要用虚析构函数呢?通常情况下,程序员的经验是,当类中存在虚函数时要把析构函数写成virtual,因为类中存在虚函数,就说明它有想要让基类指针或引用指向派生类对象的情况,此时如果派生类的构造函数中有用new/malloc动态产生的内存,那么在其析构函数中务必要delete这个数据,但是一般的像以上这种程序,这种操作只调用了基类的析构函数,而标记成虚析构函数的话,系统会先调用派生类的析构函数,再调用基类本身的析构函数

(6)一般情况下,在类中有指针成员的时候要写copy构造函数,赋值操作符重载和析构函数。

你可能感兴趣的:(C++,c++,算法,c语言)