关于C++类析构函数为虚函数时,如果是父类的指针用子类来new,如果发生析构时,析构函数是virtual与不是virtual有什么区别。
一、先看第一种最简单的情况吧,教科书上教的,析构函数不是virtual,正常定义一个子类对象
class student
{
public:
int *m_pInt;
student()
{
m_pInt = new int[10]; //1
memset(m_pInt, 0, 10*4);
}
~student()
{ //3
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;
}
构造函数:先调用父类构造函数,再调用子类构造函数;
再来看看析构时的顺序(教科书上写的是先调用子类的析构函数,在调用父类的,与构造过程相反)
二、析构函数是virtual,正常定义一个子类对象
析构函数最后还是调用了父类的析构(即使是虚函数)。
三、用一个父类指针去new一个子类对象,析构函数不是virtual,构造过程略过
看到只调用了父类的析构函数,此时子类的析构函数没有被调用,此时子类的析构函数中虽然有调用父类析构函数的代码,但是这里直接调用的是父类的析构函数,所以这是如果子类中析构函数有释放资源的代码,这里会造成这部分资源不被释放,有可能造成内存泄露
四、用一个父类指针去new一个子类对象,析构函数是virtual,构造过程略过
由于子类的虚构函数最后会调用父类的析构函数(不管是否为virtual,子类析构函数最后都会调用父类析构函数),所以最终父类的析构函数会得到执行。(这时调用的析构函数相当于pStu->vpTable->析构函数(),这个析构函数是构造时写入的,由于构造时使用子类类型去new,此时虚函数表中析构函数的地址是子类的析构函数地址)
最后的结论:
1、无论父类与子类的析构函数是否是virtual,子类的析构函数都会调用父类的析构函数
2、如果父类与子类的析构函数不为virtual,用一个父类指针指向一个用子类类型new的对象,delete时,直接调用父类的析构函数,这是在编译时刻就决定的。如果子类析构函数中有释放资源的代码,这是会发生资源泄漏。
3、如果父类与子类的析构函数是virtual,用一个父类指针指向一个用子类类型new的对象,delete时,这时由于是通过虚函数表调用析构函数,而虚函数表中的地址是构造时写入的,是子类的析构函数的地址,由于结论第一条,所以子类与父类的析构函数都会得到调用,不会发生资源泄漏。
*************************************************************************************************************
补充:为什么构造函数不能用虚函数
如果有这样两个类:
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()必然是一个错误。所以在构造函数内部执行时,对象还没有完全构造好,不能按虚函数方式进行调用,构造函数本身不能为虚函数也是相同的原因了。