在C++中,创建派生类对象时,基类和派生类的构造函数和析构函数的调用顺序正好相反:
对于构造函数,先执行基类的,再执行派生类的子对象成员的,最后执行派生类的构造函数体。
对于析构函数,先执行派生类的析构函数体,再执行派生类的子对象成员的,最后执行基类的。
注意:
- 对基类成员和派生类子对象成员的初始化必须在成员初始化列表中进行,新增成员的初始化既可以在成员初始化列表中进行,也可以在构造函数体中进行;
- 派生类的析构函数和没有继承关系的类中析构函数的定义完全相同,只要在函数体中负责把派生类新增的非对象成员的清理工作做好就够了,系统会自己调用基类及成员对象的析构函数,来对基类及对象成员进行清理;
- 当派生类有多个基类时,处于同一层次的各个基类的构造函数的调用顺序取决于定义派生类时声明的顺序(自左向右),而与在派生类构造函数的成员初始化列表中给出的顺序无关。
- 当派生类中有多个子对象时,各个子对象构造函数的调用顺序也取决于在派生类中定义的顺序(自前至后),而与在派生类构造函数的成员初始化列表中给出的顺序无关。
派生类构造函数和析构函数构建的原则:
- 基类的构造函数和析构函数不能被派生类继承;
- 如果基类没有定义构造函数,派生类也可以不定义构造函数,全部采用缺省的构造函数,此时派生类新增成员的初始化工作可用其它公有函数来完成;
- 如果基类定义了带有形参表的构造函数,派生类就必须定义新的构造函数,提供一个将参数传递给基类构造函数的途径,以便保证基类在初始化时获得必需的数据;
- 如果派生类的基类也是派生类,则每个派生类只负责其直接基类的构造,不负责自己的间接基类的构造;
- 派生类是否要定义析构函数与所属的基类无关,如果派生类对象在撤销时需要做清理善后工作,就需要定义新的析构函数。
派生类的数据成员是所有基类的数据成员和派生类新增的数据成员共同组成。如果派生类中还有对象成员,派生类的数据成员中还间接包含有这些对象的数据成员。
先看下面的例子:
#include "stdafx.h"
#include <iostream>
using namespace std;
class Base
{
public:
Base()
{
foo();
}
~Base()
{
printf("!base\n");
foo();
}
void foo()
{
printf("base\n");
}
};
class der:public Base
{
public:
der()
{
foo();
}
~der()
{
printf("!der\n");
foo();
}
void foo()
{
printf("der\n");
}
};
int _tmain(int argc, _TCHAR* argv[])
{
der * p = new der;
delete p;
printf("-----------------------\n");
Base * q = new der;
delete q;
return 0;
}
程序运行的结果如下:
两段代码都是使用new运算符动态创建了一个派生类对象并赋值给指针,并通过指针删除派生类对象,不同的是一个是派生类自己的指针,一个是基类的指针。
通过派生类指针删除对象的运行结果跟前面提到的析构函数的调用顺一致,比较奇怪的是为什么通过基类base指针删除派生类der对象时,没有调用派生类的析构函数?
这是因为在在公有继承中,基类对派生类及其对象的操作,只能影响到那些从基类继承下来的成员。如果想要用基类对非继承成员进行操作,则要把基类的这个操作(函数)定义为虚函数。
那么析构函数也就如此,如果它想析构子类中的重新定义或新的成员对象,那么它应该声明为虚的。
但有一点要注意:虚函数(包括虚析构函数)增加内存开销。(见http://www.dzsc.com/data/html/2010-5-20/83289.html)
也就是说:如果基类的析构函数不是虚析构函数的话,就会只调用基类base中的析构函数释放资源, 而不会调用派生类der的析构函数, 这时派生类der的资源没有被释放,从而导致大量内存泄露。
因此解决这个问题的方法就是把基类的析构函数声明为虚的,即在虚构函数前添加关键字virtual,定义为虚析构函数时当用delete(delete只能释放用new动态分配的内存)释放派生类的资源时就会根据基类的析构函数自动调用派生类中的析构函数释放派生类的资源。并且这种虚属性是自动被继承的,即只要基类中的析构函数是虚析构函数 ,则该基类的派生类中的析构函数自动为虚析构函数 ,虽然派生类中的析构函数前没有virtual关见字,析构函数名字也不一样,但派生类中的析 构函数被自动继承为虚析构函数 。
#include "stdafx.h"
#include <iostream>
using namespace std;
class Base
{
public:
Base()
{
foo();
}
virtual ~Base()
{
printf("!base\n");
foo();
}
void foo()
{
printf("base\n");
}
};
class der:public Base
{
public:
der()
{
foo();
}
~der()
{
printf("!der\n");
foo();
}
void foo()
{
printf("der\n");
}
};
class derder : public der
{
public:
derder()
{
foo();
}
~derder()
{
printf("!derder\n");
foo();
}
void foo()
{
printf("derder\n");
}
};
int _tmain(int argc, _TCHAR* argv[])
{
der * q = new der;
delete q;
printf("-----------------------\n");
Base * p = new der;
delete p;
printf("-----------------------\n");
der * r = new derder;
delete r;
return 0;
}
运行结果:
接下来我们来考虑如果把foo函数设置为虚函数又会出现什么情况呢?
#include "stdafx.h"
#include <iostream>
using namespace std;
class Base
{
public:
Base()
{
foo();
}
~Base()
{
printf("!base\n");
foo();
}
virtual void foo()
{
printf("base foo\n");
}
};
class der:public Base
{
public:
der()
{
foo();
}
~der()
{
printf("!der\n");
foo();
}
virtual void foo()
{
printf("der foo\n");
}
};
int _tmain(int argc, _TCHAR* argv[])
{
der * q = new der;
delete q;
printf("-----------------------\n");
Base * p = new der;
delete p;
return 0;
}
运行结果:
遵循的原则还是基类的析构函数是否是虚的。