您的位置:首页 > 编程语言 > C语言/C++

分析C++方式构造函数调用虚函数的问题

2011-04-17 00:00 447 查看
最近在看JAVA,因为JAVA是运行期绑定,所以里面提到了一个有趣的问题,就是在构造函数里面运行虚函数的问题。

构造函数里可以运行虚函数吗?我们先不讨论实际项目中是否有这个必要(至少我还没碰到过,也许即便碰到了也有其他的解决办法。),单就构造函数里调用虚拟函数的情况来做些分析。

在JAVA中,如果在构造函数中调用虚拟函数的话,是可以编译通过的,也不会出现运行期错误,但他的运行结果也许不是你想要的。在JAVA当中,由于是运行期绑定,而构造函数执行的虚拟函数将是衍生类中的函数(假如衍生类对该虚拟函数进行了覆盖的话),但这里我们会有疑问了,构造函数是从父类开始构建的,也就是此时衍生类还未构造出来,所以如果此时执行衍生类的函数无疑是不安全的。所以JAVA中的做法不能说没有问题的。

那么在VC++当中情形会一样吗?我很好奇,所以做了测试。

首先,我们使用如下代码测试:

#include "stdafx.h"

class VirClassA
{
public:
VirClassA(){
printf("VirClassA Construct Before/r/n");
FunA();
printf("VirClassA Construct After/r/n");
}
virtual	void FunA(){
printf("VirClassA FunA/r/n");
}
};

class VirClassB : public VirClassA
{
public:
VirClassB(){
printf("VirClassB Construct Before/r/n");
FunA();
printf("VirClassB Construct After/r/n");
}
virtual	void FunA(){
printf("VirClassB FunA/r/n");
}
};

int _tmain(int argc, _TCHAR* argv[])
{
VirClassB a;

VirClassB *pB = &a;
VirClassA *pA = pB;

printf("pB->FunA() Before/r/n");
pB->FunA();

printf("pA->FunA() Before/r/n");
pA->FunA();
getchar();
return 0;
}


以上代码运行结果如下:



从中我们可以看到,在VC++的实现方法和JAVA是不同的,在构造对象a时,虽然该对象是VirClassB类型,但在父类VirClassA构造函数中虽然调用的是虚拟函数,而且该虚拟函数被VirClassB覆盖了,但实际调用的还是VirClassA中的FunA,这个从道理上也说得过去,因为虽然最终要构造的对象是属于VirClassB的,但在此时此刻,它还属于VirClassA,所以调用的是VirClassA的FunA也合情合理。这当然比JAVA的做法更科学一些,至少不会出现JAVA中的莫名其妙的错误了。

当构建全部完成后,虚函数的执行恢复了正常,这从后面两个执行结果都是VirClassB FunA,可以看出来.

其实看源代码可以发现,其实虚函数表的指针赋值是发生在构造函数当中,且在构造函数执行之前,所以就不难解释上面的结果了。

但我还不想就此打住,因为我们不要忽视了另一个情况,就是纯虚函数。请看下面的代码:

#include "stdafx.h"

class VirClassA
{
public:
VirClassA(){
printf("VirClassA Construct Before/r/n");
FunA();
printf("VirClassA Construct After/r/n");
}
virtual	void FunA()=0;
};

class VirClassB : public VirClassA
{
public:
VirClassB(){
printf("VirClassB Construct Before/r/n");
FunA();
printf("VirClassB Construct After/r/n");
}
virtual	void FunA(){
printf("VirClassB FunA/r/n");
}
};

int _tmain(int argc, _TCHAR* argv[])
{
VirClassB a;

VirClassB *pB = &a;
VirClassA *pA = pB;

printf("pB->FunA() Before/r/n");
pB->FunA();

printf("pA->FunA() Before/r/n");
pA->FunA();
getchar();
return 0;
}


我们将VirClassA的代码FunA修改为纯虚函数,并在VirClassA构造函数中调用,这在程序编写按理也是可以的吧?因为纯虚函数本来就是供衍生类覆盖的,在父类中调用也说得过去的。那么我们开始编译运行看看。

哦?~!出现了编译错误。

无法解析的外部符号 "public: virtual void __thiscall VirClassA::FunA(void)" (?FunA@VirClassA@@UAEXXZ) ,该符号在函数 "public: __thiscall VirClassA::VirClassA(void)" (??0VirClassA@@QAE@XZ) 中被引用

哇!不禁要惊叹编译器的聪明!因为构造函数中调用的将是VirClassA中的FunA,这在前面已经说过。你既然没定义,那么理应给一个编译错误,到这里VC++编译器还是值得我们信赖的。

那么再来一个测试例子。

#include "stdafx.h"

class VirClassA
{
public:
VirClassA(){
printf("VirClassA Construct Before/r/n");
FunA();
printf("VirClassA Construct After/r/n");
}
virtual	void FunA(){
printf("VirClassA FunA/r/n");
FunB();
}
virtual void FunB() = 0;
};

class VirClassB : public VirClassA
{
public:
VirClassB(){
printf("VirClassB Construct Before/r/n");
FunA();
printf("VirClassB Construct After/r/n");
}
virtual	void FunA(){
printf("VirClassB FunA/r/n");
}
virtual	void FunB(){
printf("VirClassB FunB/r/n");
}
};

int _tmain(int argc, _TCHAR* argv[])
{
VirClassB a;

VirClassB *pB = &a;
VirClassA *pA = pB;

printf("pB->FunA() Before/r/n");
pB->FunA();

printf("pA->FunA() Before/r/n");
pA->FunA();
getchar();
return 0;
}


大家可以测试下这个例子,看看出现了什么情况?没错,程序将当掉。

出现了运行期错误:



可以看到在FunA中调用一个纯虚函数FunB()时当掉了。将出现一个纯虚函数调用运行时错误。

所以编译器是没法识别绕了弯的构造函数里做纯虚函数调用。不过好在还有一个运行时错误,还是比JAVA高明一些。因为我们知道JAVA那种方式将出现一些很难识别的错误。

所以通过以上分析,大家最好还是不要在构造函数里调用虚函数,而且程序大师都是建议构造函数做尽可能简单的事情。相对于JAVA来说,VC++的做法应该是更安全的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: