析构函数
先看两个例子 注意在多态下的析构函数调用
#include<iostream>
#include<list>
#include<string>
#include<vector>
using namespace std;
class base
{
public:
~base()
{
cout<<"父类析构函数被调用"<<endl;
}
};
class derived:public base
{
public:
char *m_str;
derived()
{
m_str = new char(100);
}
~derived()
{
cout<<"子类的析构函数被调用"<<endl;
delete m_str;
}
};
int main()
{
base *p = new derived();//父类指针指向子类对象
delete p ;//这是只调用了父类的析构函数,未执行子类的析构函数delete m_str;造成内存泄漏
base *m = new base();//父类指针指向父类
delete m;//这是不会造成内存泄漏
return 0;
}
输出:
父类析构函数被调用
父类析构函数被调用
请按任意键继续. . .
此时的第一个例子中 子类的析构没有调用
#include <iostream>
using namespace std;
#include<list>
#include<string>
#include<vector>
using namespace std;
class base
{
public:
virtual ~base()
{
cout << "父类析构函数被调用" << endl;
}
};
class derived :public base
{
public:
char* m_str;
derived()
{
m_str = new char(100);
}
~derived()
{
cout << "子类的析构函数被调用" << endl;
delete m_str;
}
};
int main()
{
base * p = new derived();//父类指针指向子类对象
delete p;//这是只调用了父类的析构函数,未执行子类的析构函数delete m_str;造成内存泄漏
base* m = new base();//父类指针指向父类
delete m;//这是不会造成内存泄漏
return 0;
}
结果:
输出:
子类的析构函数被调用
父类析构函数被调用
请按任意键继续. . .
原因:
1、如果父类的析构函数不是虚函数,则不会触发动态绑定(多态),结果就是只会调用父类的析构函数,而不会调用子类的析构函数,从而可能导致子类的内存泄漏(如果子类析构函数中存在free delete 等释放内存操作时);
2、如果父类的析构函数是虚函数,则子类的析构函数一定是虚函数(即使是子类的析构函数不加virtual,这是C++的语法规则),则会在父类指针或引用指向一个子类时,触发动态绑定(多态),析构实例化对象时,若是子类则会执行子类的析构函数,同时,编译器会在子类的析构函数中插入父类的析构函数,最终实现了先调用子类析构函数再调用父类析构函数。
构造函数
构造函数不能是虚函数
-
从vptr角度解释
虚函数的调用是通过虚函数表来查找的,而虚函数表由类的实例化对象的vptr指针(vptr可以参考C++的虚函数表指针vptr)指向,该指针存放在对象的内部空间中,需要调用构造函数完成初始化。如果构造函数是虚函数,那么调用构造函数就需要去找vptr,但此时vptr还没有初始化! -
从多态角度解释
虚函数主要是实现多态,在运行时才可以明确调用对象,根据传入的对象类型来调用函数,例如通过父类的指针或者引用来调用它的时候可以变成调用子类的那个成员函数。而构造函数是在创建对象时自己主动调用的,不可能通过父类的指针或者引用去调用。那使用虚函数也没有实际意义。
在调用构造函数时还不能确定对象的真实类型(由于子类会调父类的构造函数);并且构造函数的作用是提供初始化,在对象生命期仅仅运行一次,不是对象的动态行为,没有必要成为虚函数