由架构中基类的设计想到的......
现在,有三个类,类的定义如下
#includeusing namespace std;class CA{public: CA(){ cout << "CA constructor" << endl; } ~CA(){ cout << "CA destructor" << endl; }};class CB:public CA{public: CB(){ cout << "CB constructor" << endl; } ~CB(){ cout << "CB destructor" << endl; }};class CC:public CB{public: CC(){ cout << "CC constructor" << endl; } ~CC(){ cout << "CC destructor" << endl; }};int main(int argc, char* argv[]){ CC c; return 0;}
CA是爷爷,CB是爸爸,CC是儿子。
那么任何一本C++的书都会讲,构造函数的调用顺序是CA CB CC,析构函数的调用顺序是CC,CB,CA,什么???你的书没讲,靠,扔了吧。
于是,这个程序运行结果是
CA constructorCB constructorCC constructorCC destructorCB destructorCA destructor
靠,太简单了,一个鸡蛋飞过来了,:(
继续……………………
(2) 再做第二个试验之前,先做一点小小修改
//~CA(){ cout << "CA destructor" << endl; }virtual ~CA(){ cout << "CA destructor" << endl; }
修改main 代码如下
int main(int argc, char* argv[]){ CA* pt = new CC; delete pt; return 0;}
yeah结果一模一样哦
CA constructorCB constructorCC constructorCC destructorCB destructorCA destructor
但是如果把virtual ~CA(){cout<<"CA destructor"<<endl;}的virtual 去掉,
那么(2)中的运行结果为
CA constructorCB constructorCC constructorCA destructor
只调了CA的析构函数哦,出问题了这样的话,就会出现基类的构造函数调用了,但是派生类的构造函数没调用,对象的派生部分不会被销毁,这将导致资源泄漏!!
所以我们在设计一个类的时候,如果类至少拥有一个虚函数,或者说基类被设计用于多态,在这种情况下,一个派生类的对象可能通过一个基类指针来进行操作,然后进行销毁,如果这样的话,那么这个基类的析构函数要设置成虚拟的,有些类虽然是基类,但是不是用于多态的,没有虚函数,没有被设计成允许经由基类接口对派生类对象进行操作,那么也无需设成虚析构函数,毕竟增加了开销。
好了,解释清楚了,我们也知道怎么做了,继续试验。
(3)保留CA中的虚析构函数
修改main 代码如下
int main(int argc, char* argv[]){ CB * pt = new CC; delete pt; return 0;}
运行结果
CA constructorCB constructorCC constructorCC destructorCB destructorCA destructor
取消CA中的虚析构函数,那么,CA,CB,CC中没有虚析构函数
class CA{public: CA(){ cout << "CA constructor" << endl; } ~CA(){ cout << "CA destructor" << endl; }};class CB:public CA{public: CB(){ cout << "CB constructor" << endl; } ~CB(){ cout << "CB destructor" << endl; }};那么 3 中代码运行结果如下
CA constructorCB constructorCC constructorCB destructorCA destructor
可以看到,只调到CB的析构哦(还有他的父类CA的析构)
继续试验,CA,CB,CC中,只有CB是虚析构函数
class CA{public: CA(){ cout << "CA constructor" << endl; } ~CA(){ cout << "CA destructor" << endl; }};class CB:public CA{public: CB(){ cout << "CB constructor" << endl; } virtual ~CB(){ cout << "CB destructor" << endl; }};class CC:public CB{public: CC(){ cout << "CC constructor" << endl; } ~CC(){ cout << "CC destructor" << endl; }};int main(int argc, char* argv[]){ CB * pt = new CC; delete pt; return 0;}3 中代码运行如下
CA constructorCB constructorCC constructorCC destructorCB destructorCA destructor
所以,如果是CB指向派生类,只要CB或者CB的基类中存在虚析构函数,那么也是所有的析构函数都调用的了
继续………………
(4)修改main 代码如下
int main(int argc, char* argv[]){ CA * pt = new CC; delete pt; return 0;}
如果A的析构函数是虚的,那么情况如2,不多说了
如果是CA的析构函数不是虚的,而CB或者CC的析构函数是虚拟的,即类似:
class CA{public: CA(){ cout << "CA constructor" << endl; } ~CA(){ cout << "CA destructor" << endl; }};class CB:public CA{public: CB(){ cout << "CB constructor" << endl; } virtual ~CB(){ cout << "CB destructor" << endl; }};class CC:public CB{public: CC(){ cout << "CC constructor" << endl; } ~CC(){ cout << "CC destructor" << endl; }};int main(int argc, char* argv[]){ CA * pt = new CC; delete pt; return 0;}
那么在调用delete p;情况分为两种情况:
(一) VC2010会出现提示错误
Expression:_BLOCK_TYPE_IS_VALID(pHead->nBlockUse) 是在释放的时候出现这样的错误上网查了一下,_BLOCK_TYPE_IS_VALID是用来检测有效性宏中的一个,这个错误说明指针使用出现了问题
后来想了一想,应该是因为继承类中出现了虚函数,所以多了一个指向虚函数表的指针,而基类中一个虚函数都没有,所以也没有这个指针啦所以在delete的时候就出现了内存错误,事实证明,这个猜想应该是站得住脚的,在CA中添加一个虚函数,即使的空的虚函数,也不会出现 错误 。关于这个问题,我想在下次继续讨论吧,这里不深入进去了。
回到正题,在CA中添加一个空的,任意的虚拟函数以后,运行正确了,运行结果是 CA constructor CB constructor CC constructor
CA desstructor
这与(2)中的情况是一样的,只要CA的析构函数不是虚拟的,就只能调用CA的析构了
(二)GCC 只会调用CA的析构函数
最后来看一种非常 Bt 的做法 (5) CA CB CC 中的析构函数,谁是虚拟的,无所谓,随便
修改main 代码如下
int main(int argc, char* argv[]){ void * pt = new CC; delete pt; return 0;}
运行结果 CA constructor CB constructor CC constructor
下面呢???下面没有了,晕……………………
这种情况,构造了一个cc的对象,然后呢,居然没有调析构函数,直接把申请的释放了,最好不要这样用咯。
好了,最后,上面,基本上把所有的,我能想到的情况都整理了一下,
构造函数顺序:
class A { public: A() { cout<<"A()"<<endl; } };
class B:public A { public: B() { cout<<"B()"<<endl; } };
class C { public: C() { cout<<"C()"<<endl; } B a;
}; int main(void) {
C c; return 0; }
如上代码输出:
A() B() C()
表明:成员对象最先构造,接着是基类构造,再是自身构造.析构时正好相反。
总结一下:
如C1 * p = new C2(); delete p;这样的代码
这里,C1是C2的基类,C1可能是C2的爸爸,可能是爷爷,可能是爸爸的爷爷,可能是爷爷的爷爷…………………………
那么首先,调用的构造函数是 从C2的第一个祖先一直到C2………………。和C1是什么没关系
在delete p的时候,那么有以下几种情况:1) C1或者C1的祖先(基类)中,含有虚析构函数,那么调用的析构函数的顺序是从C2一直到C2的第一个祖先(#add最开始的祖先) 2)如果C1或者C1的祖先中,没有一个是类是含有虚析构函数的,那么调用的是从C1一直到C1的(也是C2的)第一个祖先的(#add表述有待细思) 3)如果C1是void,那就什么析构都不调用了。
如果一个类,作为多态的基类,那么尽量把析构函数声明成虚拟的,不然………………,
好了,就此打住吧,关于4中的错误,下次再谈论吧
另外,可见虚函数是用来实现多态的,但是虚析构函数是一种特殊的虚函数,它的目的主要是为了在多态中防止资源泄露。只要C1或者C1的祖先中有一个含有虚析构函数,那么该调到的析构函数都会调用到。如果C1或C1的祖先中没有一个含有虚析构函数,那么只会按照C1类型处理,不关心具体指到的类型。
参考书籍 《Effective C++》