C++中构造函数能调用虚函数吗?(答案是语法可以,输出错误),但Java里居然可以

环境:XPSP3 VS2005

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

[cpp] view plain copy

  1. class Base
  2. {
  3. public:
  4. Base()
  5. {
  6. Fuction();
  7. }
  8. virtual void Fuction()
  9. {
  10. cout << "Base::Fuction" << endl;
  11. }
  12. };
  13. class A : public Base
  14. {
  15. public:
  16. A()
  17. {
  18. Fuction();
  19. }
  20. virtual void Fuction()
  21. {
  22. cout << "A::Fuction" << endl;
  23. }
  24. };
  25. // 这样定义一个A的对象,会输出什么?
  26. 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。

        继续跟踪,流程又回到A的构造函数中,再次注意加粗部分的代码,从基类Base的构造函数返回后,在A的构造函数中,重设了虚表指针,现在的虚表指针是(0x417700h),同样调用Fuction的时候直接调用了A::Fuction函数,并没有使用虚机制,而且此时虚表0x417700h指向的位置存放的0x41110e正是A::Fuction的地址。

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

其实事情就是这么简单。

http://blog.csdn.net/magictong/article/details/6734241

时间: 2024-10-05 23:46:39

C++中构造函数能调用虚函数吗?(答案是语法可以,输出错误),但Java里居然可以的相关文章

09 构造函数能调用虚函数吗?

[本文链接] http://www.cnblogs.com/hellogiser/p/whether-constructor-can-call-virtual-function.html [题目] 构造函数可以调用虚函数吗?语法上通过吗?语义上可以通过吗? [分析] 构造函数调用虚函数(virtual function),语法上可以通过(程序可以正常执行),但是语义上通不过(执行结果不是我们想要的) 请看以下代码 C++ Code 12345678910111213141516171819202

C++ Daily 《3》----构造函数可否是虚函数

C++ 中构造函数可否是虚函数? 绝不要!! 而且,在构造函数中调用虚函数也是不提倡的行为,因为会引发预想不到的结果. 因为,在 derived class 对象构造的过程中,首先调用的是基类的构造函数,等基类构造结束,才开始构造派生类的部分. 这个过程中,编译器将该对象视为 基类,而非派生类(因为,派生类对象的专属部分还未初始化!!!) 所以,如果在构造函数中调用虚函数,虚函数调用的一定是基类的虚函数,对象的行为就如同是基类的对象一样. 或者可以这么说:这个时候,虚函数不再是 虚函数... 参

C++面试题1:构造函数和虚构函数中能否调用虚函数?

C++面试题1:构造函数和虚构函数中能否调用虚函数? 构造函数跟虚构函数里面都可以调用虚函数,编译器不会报错. C++ primer中说到最好别用 由于类的构造次序是由基类到派生类,所以在构造函数中调用虚函数,虚函数是不会呈现出多态的 类的析构是从派生类到基类,当调用继承层次中某一层次的类的析构函数时意味着其派生类部分已经析构掉,所以也不会呈现多态 因此如果在基类中声明的纯虚函数并且在基类的析构函数中调用之,编译器会发生错误. class Base { public: Base() { Fuct

C++构造函数中调用虚函数

谈谈关于构造函数中调用虚函数的情况,仅讨论单继承,不考虑虚拟继承和多重继承. 测试平台:VS2013 + Win7X64 一个例子: #include <stdlib.h> #include <stdio.h> class Base { private: int __data; public: Base() { this->Func(); } public: virtual void Func() { printf("Base::Func"); } };

在构造函数和析构函数中调用虚函数------新标准c++程序设计

在构造函数和析构函数中调用虚函数不是多态,因为编译时即可确定调用的是哪个函数.如果本类有该函数,调用的就是本类的函数:如果本类没有,调用的就是直接基类的函数:如果基类没有,调用的就是间接基类的函数,以此类推.例如: #include<iostream> using namespace std; class A { public: virtual void hello(){cout<<"A::hello()"<<endl;} virtual void

EC笔记,第二部分:9.不在构造、析构函数中调用虚函数

9.不在构造.析构函数中调用虚函数 1.在构造函数和析构函数中调用虚函数会产生什么结果呢? #include <iostream> using namespace std; class cls1{ public: cls1(){ newMake(); }; ~cls1(){ deleteIt(); }; virtual void newMake(){ cout<<"cls1 make"<<endl; } virtual void deleteIt()

派生类的构造函数和析构函数和多继承中的二义性与虚函数

析构函数主要作用是对数据成员初始化. 1派生类的构造函数 派生类的构造函数定义的一般形式为: 派生类名::派生类名(基类所需形参,本类成员所需形参):基类1(基类1 参数表),基类2(基类2 参数表),···,基类n(基类n 参数表),对象成员1(对象1 参数表),对象成员2(对象2 参数表),···,对象成员m(对象m 参数表){ //本类基本类型数据成员初始化 } 如果使用基类无参构造函数,派生类构造函数形参表中不包含供给基类构造函数的参数.此时,系统会调用基类的默认构造函数.如果使用对象数

C++中为什么构造函数不能是虚函数,析构函数是虚函数

一, 什么是虚函数? 简单地说,那些被virtual关键字修饰的成员函数,就是虚函数.虚函数的作用,用专业术语来解释就是实现多态性(Polymorphism),多态性是将接口与实现进行分离:用形象的语言来解释就是实现以共同的方法,但因个体差异而采用不同的策略. 所谓虚函数就是多态情况下只执行一个,而从继承的概念来讲,总是要先构造父类对象,然后才能是子类对象,如果构造函数设为虚函数,那么当你在构造父类的构造函数时就不得不显示的调用构造,还有一个原因就是为了防错,试想如果你在子类中一不小心重写了个跟

Item 9:在析构/构造时不要调用虚函数 Effective C++笔记

Item 9: Never call virtual functions during construction or destruction. 父类构造期间,对虚函数的调用不会下降至子类.如果这并非你的意图,请不要这样做! 这个问题阿里实习面试曾经问到过,看这篇文章: 2014阿里巴巴面试经历 看Scott Meyers举的例子: class Transaction { // base class for all public: // transactions Transaction(){ /