在多态场景中,可通过基类的指针指向子类对象,并完成对子类对象的成员函数调用;在函数对象析构时,根据继承顺序,往往是先调用子类的析构函数,然后调用基类的析构函数,但是在多态场景中,可能会出现以下错误的情况:
#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;
}
上述程序看似没问题,但是执行下来发现,输出结果如下:
可以发现此处在析构时仅仅调用了基类的析构函数,并没有调用子类的析构函数,在上述例子中并不会造成什么太坏影响,但是如果在子类的析构函数中做了内存释放、资源清楚等事情,上述情况就会造成内存泄漏,具体见下面的示例。
#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;
}
程序执行结果:
在上述示例中,可以发现在子类的构造函数中申请了100 byte的内存空间给指针p,在析构函数中释放了该内存,因此在执行子类构造函数后必须执行其析构函数,否则会造成内存泄漏,上述的执行结果显然没有调用子类的析构函数,必然造成p申请的内存空间得不到释放,造成内存泄漏,如何解决?基类析构函数需要加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;
}
程序执行结果:
上述示例中,在基类的析构函数声明时添加了virtual关键字,可见执行结果中调用了子类的析构函数,自然在子类析构函数中释放了p申请的内存,避免了内存泄漏。
(1)基类的的析构函数不是虚函数的话,删除指针时,只有基类的内存被释放,派生类的没有,这样就内存泄漏了。
(2)析构函数不是虚函数的话,直接按指针类型调用该类型的析构函数代码,因为指针类型是基类,所以直接调用基类析构函数代码。
(3)当基类指针指向派生类的时候,如果析构函数不声明为虚函数,在析构的时候,不会调用派生类的析构函数,从而导致内存泄露。
(4)子类对象创建时先调用基类构造函数然后在调用子类构造函数,在清除对象时顺序相反,所以delete pbase只清除了基类,而子类没有清除。
(5) 什么时候才要用虚析构函数呢?通常情况下,程序员的经验是,当类中存在虚函数时要把析构函数写成virtual,因为类中存在虚函数,就说明它有想要让基类指针或引用指向派生类对象的情况,此时如果派生类的构造函数中有用new/malloc动态产生的内存,那么在其析构函数中务必要delete这个数据,但是一般的像以上这种程序,这种操作只调用了基类的析构函数,而标记成虚析构函数的话,系统会先调用派生类的析构函数,再调用基类本身的析构函数。
(6)一般情况下,在类中有指针成员的时候要写copy构造函数,赋值操作符重载和析构函数。