C++中构造函数能调用虚函数吗?

时间:2022-09-09 18:48:14

C++中构造函数能调用虚函数吗?

分类: C++ Win32   4481人阅读  评论(8)  收藏  举报

      环境:XPSP3 VS2005

        今天黑总给应聘者出了一个在C++的构造函数中调用虚函数的问题,具体的题目要比标题复杂,大体情况可以看如下的代码:

[cpp]  view plain copy
  1. class Base  
  2. {  
  3. public:  
  4.     Base()  
  5.     {  
  6.         Fuction();  
  7.     }  
  8.   
  9.     virtual void Fuction()  
  10.     {  
  11.         cout << "Base::Fuction" << endl;  
  12.     }  
  13. };  
  14.   
  15. class A : public Base  
  16. {  
  17. public:  
  18.     A()  
  19.     {  
  20.         Fuction();  
  21.     }  
  22.   
  23.     virtual void Fuction()  
  24.     {  
  25.         cout << "A::Fuction" << endl;  
  26.     }  
  27. };  
  28.   
  29. // 这样定义一个A的对象,会输出什么?  
  30. A a;  

        首先回答标题的问题,调用当然是没有问题的,但是获得的是你想要的结果吗?或者说你想要什么样的结果?

        有人说会输出:

[html]  view plain copy
  1. A::Fuction  
  2. A::Fuction  

        如果是这样,首先我们回顾下C++对象模型里面的构造顺序,在构造一个子类对象的时候,首先会构造它的基类,如果有多层继承关系,实际上会从最顶层的基类逐层往下构造(虚继承、多重继承这里不讨论),如果是按照上面的情形进行输出的话,那就是说在构造Base的时候,也就是在Base的构造函数中调用Fuction的时候,调用了子类A的Fuction,而实际上A还没有开始构造,这样函数的行为就是完全不可预测的,因此显然不是这样,实际的输出结果是:

[html]  view plain copy
  1. Base::Fuction  
  2. A::Fuction  

        据说在JAVA中是上一种输出(感觉有点匪夷所思)。

        我们来单步看一下到底发生了什么?在A的构造函数里面首先会去调用Base的构造函数,Base的构造函数如下:

class Base
{
public:
 Base()
00411600  push        ebp  
00411601  mov         ebp,esp 
00411603  sub         esp,0CCh 
00411609  push        ebx  
0041160A  push        esi  
0041160B  push        edi  
0041160C  push        ecx  
0041160D  lea         edi,[ebp-0CCh] 
00411613  mov         ecx,33h 
00411618  mov         eax,0CCCCCCCCh 
0041161D  rep stos    dword ptr es:[edi] 
0041161F  pop         ecx  
00411620  mov         dword ptr [ebp-8],ecx 
00411623  mov         eax,dword ptr [this] 
00411626  mov         dword ptr [eax],offset Base::`vftable' (41770Ch)
 {
  Fuction();
0041162C  mov         ecx,dword ptr [this] 
0041162F  call        Base::Fuction (4111A9h)

 }
00411634  mov         eax,dword ptr [this] 
00411637  pop         edi  
00411638  pop         esi  
00411639  pop         ebx  
0041163A  add         esp,0CCh 
00411640  cmp         ebp,esp 
00411642  call        @ILT+460(__RTC_CheckEsp) (4111D1h) 
00411647  mov         esp,ebp 
00411649  pop         ebp  
0041164A  ret

        从单步跟踪来看,注意黑色加粗的那部分汇编代码,ecx中存放的是对象的地址(0x0012ff60,我的机器上的情况看下图,有图有真相),首先是设置vtable的地址到对象的前四个字节(不同的编译器可能不同),然后就直接调用了Base::Fuction函数,并没有走虚机制,而我们此时看虚表中的状态,虚表已经填充的是0x4111a9,注意虚表的地址0x0041770c,而此时对象地址0x0012FF60前四个字节存放的正是0x0041770c。
C++中构造函数能调用虚函数吗?
        继续跟踪,流程又回到A的构造函数中,再次注意加粗部分的代码,从基类Base的构造函数返回后,在A的构造函数中,重设了虚表指针,现在的虚表指针是(0x417700h),同样调用Fuction的时候直接调用了A::Fuction函数,并没有使用虚机制,而且此时虚表0x417700h指向的位置存放的0x41110e正是A::Fuction的地址。

C++中构造函数能调用虚函数吗?

class A : public Base
{
public:
 A()
00411590  push        ebp  
00411591  mov         ebp,esp 
00411593  sub         esp,0CCh 
00411599  push        ebx  
0041159A  push        esi  
0041159B  push        edi  
0041159C  push        ecx  
0041159D  lea         edi,[ebp-0CCh] 
004115A3  mov         ecx,33h 
004115A8  mov         eax,0CCCCCCCCh 
004115AD  rep stos    dword ptr es:[edi] 
004115AF  pop         ecx  
004115B0  mov         dword ptr [ebp-8],ecx 
004115B3  mov         ecx,dword ptr [this] 
004115B6  call        Base::Base (411140h) 
004115BB  mov         eax,dword ptr [this] 
004115BE  mov         dword ptr [eax],offset A::`vftable' (417700h)
 {
  Fuction();
004115C4  mov         ecx,dword ptr [this] 
004115C7  call        A::Fuction (41110Eh)
 }
004115CC  mov         eax,dword ptr [this] 
004115CF  pop         edi  
004115D0  pop         esi  
004115D1  pop         ebx  
004115D2  add         esp,0CCh 
004115D8  cmp         ebp,esp 
004115DA  call        @ILT+460(__RTC_CheckEsp) (4111D1h) 
004115DF  mov         esp,ebp 
004115E1  pop         ebp  
004115E2  ret


        其实事情就是这么简单。

 

更多 0
上一篇: CMarkup成员方法简介下一篇: C语言编译全过程
2
1
相关主题推荐
c++  编译器  对象  继承  微软
相关博文推荐
乱水bnuoj
C++继承的三种方式
OpenGL进阶(十八) - 从零搭建基...
SQLite编程相关
【Leetcode】Word Ladder
Makefile:2: *** 遗漏分隔...
linux下的C++编程
C++之字节对齐与结构体大小(转)
查看评论
8楼  shifters 2011-08-31 20:36发表 [回复]
C++中构造函数能调用虚函数吗?
2楼正解,c++中已有规定
7楼  爱技术的华仔 2011-08-31 19:56发表 [回复]
C++中构造函数能调用虚函数吗?
其实只要将C++对象的构造顺序和机制想通就能推断出来了
6楼  谭海燕 2011-08-31 12:47发表 [回复]
C++中构造函数能调用虚函数吗?
你的分析虚函数表的指针是什么时候出来的。
5楼  androidrobot1 2011-08-31 11:41发表 [回复]
C++中构造函数能调用虚函数吗?
汇编不懂啊,感觉因该是第二中结果
4楼  wangjiabin2007 2011-08-31 11:11发表 [回复]
C++中构造函数能调用虚函数吗?
不错
3楼  iaccepted 2011-08-31 09:50发表 [回复]
C++中构造函数能调用虚函数吗?
表示直接输出正确,基础学的还是不错的,呵呵!
2楼  dbzhang800 2011-08-31 09:45发表 [回复]
C++中构造函数能调用虚函数吗?
恩,C++标准2.7对此有明文规定

Member functions, including virtual functions (10.3), can be called during construction or destruction (12.6.2).
When a virtual function is called directly or indirectly from a constructor (including the mem-initializer or
brace-or-equal-initializer for a non-static data member) or from a destructor, and the object to which the
call applies is the object under construction or destruction, the function called is the one defined in the
constructor or destructor’s own class or in one of its bases, but not a function overriding it in a class derived
from the constructor or destructor’s class, or overriding it in one of the other base classes of the most derived
object (1.8). If the virtual function call uses an explicit class member access (5.2.5) and the object-expression
refers to the object under construction or destruction but its type is neither the constructor or destructor’s
own class or one of its bases, the result of the call is undefined.