C++学习笔记(17)——虚函数与构造函数、析构函数

来源:互联网 发布:大数据 crm 市场规模 编辑:程序博客网 时间:2024/04/30 04:20
 

虚函数与构造函数、析构函数

构造函数:为对象分配存储空间,使一个对象初始化;
析构函数:在该对象生命期完结时做相应的扫尾工作并释放由构造函数分配的内存;
构造函数不能是虚函数的原因:
l 从概念上来说,如前所述,虚函数机制只有在应用于地址时才有效,因为地址在编译阶段提供的类型信息不完全。构造函数的功能是为一个对象在内存中分配空间,也就是说,此时该对象的类型已经确定了,编译系统确切的知道应该调用哪一个类的构造函数,不需要也不可能应用动态绑定。
l 从实现上来说,每个对象的VPTR是需要构造函数来初始化的(当然是由编译系统自动加进去的代码来实现),在构造函数没有调用之前,VPTR没有形成,根本就不可能实现动态绑定。
当构造函数内部有虚函数时,会出现什么情况呢?结果是,只有在该类中的虚函数版本被调用,也就是说,在构造函数中,虚函数机制不起作用了,调用虚函数如同调用一般的成员函数一样
析构函数可以是虚函数:
l 析构函数可以是虚函数,而且应该被声明为虚函数。与一般成员函数相似,析构函数被调用时,对象的构造已经完成,VPTRVTABLE也已被正确初始化,因此虚析构函数在实现上是可能的。
l 从设计角度来看,析构函数的任务是释放内存,因此它必须确切知道被释放的对象的类型,否则可能破坏有用的数据,产生不可预知的后果。例如,我们用基类指针指向了派生类对象,那么释放内存时,必须是释放派生类对象的存储空间。所以,析构函数经常被声明为虚函数。由于效率上的原因,并不把析构函数缺省为虚函数。但作为一条实践经验,可以给有虚函数的每个基类声明虚析构函数。
  当析构函数内部有虚函数时,又如何工作呢?与构造函数相同,只有“局部”的版本被调用。但是,行为相同,原因是不一样的构造函数只能调用“局部”版本,是因为调用时还没有派生类版本的信息。析构函数则是因为派生类版本的信息已经不可靠了。我们知道,析构函数的调用顺序与构造函数相反,是从派生类的析构函数到基类的析构函数。当某个类的析构函数被调用时,其下一级的析构函数已经被调用了,相应的数据也已被丢失,如果再调用虚函数的最后一级的版本,就相当于对一些不可靠的数据进行操作,这是非常危险的。因此,在析构函数中,虚函数机制也是不起作用的

代码如下:

/**//************************************************************************
* 虚函数与构造函数、析构函数
************************************************************************/

#include<IOSTREAM.H>

class CBase
...
{
public
:
CBase()
...
{
cout<<"CBase Constructor! "
;
func();
}

~CBase()
...
{
cout<<"CBase deconstructor! "
;
func();
}

virtual void func()
...
{
cout<<"CBase::func() called! "
;
}

};

class CDerive: public
CBase
...
{
public
:
CDerive()
...
{
cout<<"CDerive Constructor! "
;
func();
}

~CDerive()
...
{
cout<<"CDerive deconstructor! "
;
func();
}

void func()
...
{
cout<<"CDerive::func() called! "
;
}

void func1()
...
{
func(); //调用虚函数

}
}
;

class CSubDerive: public
CDerive
...
{
public
:
CSubDerive()
...
{
cout<<"CSubDerive Constructor! "
;
func();
}

~CSubDerive()
...
{
cout<<"CSubDerive deconstructor! "
;
func();
}

void func()
...
{
cout<<"CSubDerive::func() called! "
;
}

};

void
main()
...
{
CSubDerive obj; //will produce "CBase::func() called!"

obj.func1(); //will produce "CSubDerive::func() called!"
}
运行结果如下:

说明:

构造函数的调用顺序是:CBase()->CDerive()->CSubDerive(),并按顺序初始化VTABLE,并且将VPTR指向最新派生的类的VTABLE。
首先调用CBase(),这时VTABLE中只有func()CBase类版本;
然后调用CDerive(),由于CDerive类没有自己定义的func(),所以VTABLE中仍然是func()CBase类版本。此时func()CSubDerive类版本尚未形成,因此只能调用“局部”版本,即CDerive类的VTABLE中存储的版本,所以输出结果是“CBase::func() called!”。
接着调用CSubDerive(),并创建最新的VTABLE,其元素是指向func()CSubDerive类版本的指针。
于是,在构造函数作用完毕之后,再调用虚函数func(),结果自然是“CSubDerive::func() called!”。
程序运行过程中,this指针的变化,注意观察VTABLE的变化:
(1)首先调用CBase(),进入构造函数CBase()内部时,this指针如下:
(2)然后调用CDerive(),进入构造函数CDerive()内部时,this指针如下:
(3)然后在构造函数CDerive()内部调用func(),则进入CBase类的func()内部时,this指针如下:

4)接着调用CSubDerive(),进入构造函数CSubDerive()内部时,this指针如下:

5)最后从构造函数CSubDerive()中返回到主函数,obj对象的内容如下(注意与(1)比较):