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

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

 

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

 

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

  1. #include "stdafx.h"  
  2.   
  3. class VirClassA  
  4. {  
  5. public:  
  6.     VirClassA(){  
  7.         printf("VirClassA Construct Before/r/n");  
  8.         FunA();  
  9.         printf("VirClassA Construct After/r/n");  
  10.     }  
  11.     virtual void FunA(){  
  12.         printf("VirClassA FunA/r/n");  
  13.     }  
  14. };  
  15.   
  16. class VirClassB : public VirClassA  
  17. {  
  18. public:  
  19.     VirClassB(){  
  20.         printf("VirClassB Construct Before/r/n");  
  21.         FunA();  
  22.         printf("VirClassB Construct After/r/n");  
  23.     }  
  24.     virtual void FunA(){  
  25.         printf("VirClassB FunA/r/n");  
  26.     }  
  27. };  
  28.   
  29. int _tmain(int argc, _TCHAR* argv[])  
  30. {  
  31.     VirClassB a;  
  32.       
  33.     VirClassB *pB = &a;  
  34.     VirClassA *pA = pB;  
  35.   
  36.     printf("pB->FunA() Before/r/n");  
  37.     pB->FunA();  
  38.   
  39.     printf("pA->FunA() Before/r/n");  
  40.     pA->FunA();  
  41.     getchar();  
  42.     return 0;  
  43. }  

 

以上代码运行结果如下:

 

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

 

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

 

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

 

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

  1. #include "stdafx.h"  
  2.   
  3. class VirClassA  
  4. {  
  5. public:  
  6.     VirClassA(){  
  7.         printf("VirClassA Construct Before/r/n");  
  8.         FunA();  
  9.         printf("VirClassA Construct After/r/n");  
  10.     }  
  11.     virtual void FunA()=0;  
  12. };  
  13.   
  14. class VirClassB : public VirClassA  
  15. {  
  16. public:  
  17.     VirClassB(){  
  18.         printf("VirClassB Construct Before/r/n");  
  19.         FunA();  
  20.         printf("VirClassB Construct After/r/n");  
  21.     }  
  22.     virtual void FunA(){  
  23.         printf("VirClassB FunA/r/n");  
  24.     }  
  25. };  
  26.   
  27. int _tmain(int argc, _TCHAR* argv[])  
  28. {  
  29.     VirClassB a;  
  30.       
  31.     VirClassB *pB = &a;  
  32.     VirClassA *pA = pB;  
  33.   
  34.     printf("pB->FunA() Before/r/n");  
  35.     pB->FunA();  
  36.   
  37.     printf("pA->FunA() Before/r/n");  
  38.     pA->FunA();  
  39.     getchar();  
  40.     return 0;  
  41. }  

 

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

 

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

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

 

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

 

那么再来一个测试例子。

  1. #include "stdafx.h"  
  2.   
  3. class VirClassA  
  4. {  
  5. public:  
  6.     VirClassA(){  
  7.         printf("VirClassA Construct Before/r/n");  
  8.         FunA();  
  9.         printf("VirClassA Construct After/r/n");  
  10.     }  
  11.     virtual void FunA(){  
  12.         printf("VirClassA FunA/r/n");  
  13.         FunB();  
  14.     }  
  15.     virtual void FunB() = 0;  
  16. };  
  17.   
  18. class VirClassB : public VirClassA  
  19. {  
  20. public:  
  21.     VirClassB(){  
  22.         printf("VirClassB Construct Before/r/n");  
  23.         FunA();  
  24.         printf("VirClassB Construct After/r/n");  
  25.     }  
  26.     virtual void FunA(){  
  27.         printf("VirClassB FunA/r/n");  
  28.     }  
  29.     virtual void FunB(){  
  30.         printf("VirClassB FunB/r/n");  
  31.     }  
  32. };  
  33.   
  34. int _tmain(int argc, _TCHAR* argv[])  
  35. {  
  36.     VirClassB a;  
  37.       
  38.     VirClassB *pB = &a;  
  39.     VirClassA *pA = pB;  
  40.   
  41.     printf("pB->FunA() Before/r/n");  
  42.     pB->FunA();  
  43.   
  44.     printf("pA->FunA() Before/r/n");  
  45.     pA->FunA();  
  46.     getchar();  
  47.     return 0;  
  48. }  

 

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

出现了运行期错误:

 

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

 

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

 

所以通过以上分析,大家最好还是不要在构造函数里调用虚函数,而且程序大师都是建议构造函数做尽可能简单的事情。相对于JAVA来说,VC++的做法应该是更安全的。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值