给出如下代码段:

  1. #include <iostream>
  2. #include "stdio.h"
  3. using namespace std;
  4. class A
  5. {
  6. public:
  7. A(int arg):m_a(arg)
  8. {
  9. cout << "constructor of A" << endl;
  10. output();
  11. }
  12. virtual void output()
  13. {
  14. cout << "output A" << endl;
  15. }
  16. virtual void display()
  17. {
  18. output();
  19. }
  20. private:
  21. int m_a;
  22. };
  23. class B : public A
  24. {
  25. public:
  26. B(int arg1, int arg2):A(arg1), m_b(arg2)
  27. {
  28. cout << "constructor of B" << endl;
  29. }
  30. virtual void output()
  31. {
  32. cout << "output B" << endl;
  33. }
  34. private:
  35. int m_b;
  36. };
  37. int main(int argc, char* argv[])
  38. {
  39. B b(1, 2);
  40. b.display();
  41. return 0;
  42. }

这段代码会输出什么?

其实这个问题本质是在A的构造函数及display函数中分别调用的是A类的output函数还是B类的output函数。

输出结果是:

constructor of A
output A
constructor of B
output B

说明A的构造函数中调用的是A类的output函数,display函数中调用的是B类的output函数。

为什么会这样呢?这跟指向虚函数表的指针(vptr)初始化时间有关。

《Inside the c++ Object model》中指出vptr初始化的时间为:

After invocation of the base class constructors but before execution of user-provided code

or the expansion of members initialized within the member initialization list.

意思是在所有基类构造函数之后,但又在自身构造函数或初始化列表之前。

vptr初始化是在初始化列表之前还是之后是跟编译器实现有关的,在VC++6.0编译器中是在初始化列表之后,从上面这段代码的部分汇编码就可以看出。

B类的构造函数的汇编码:

00401250   push        ebp
00401251   mov         ebp,esp
00401253   sub         esp,44h
00401256   push        ebx
00401257   push        esi
00401258   push        edi
00401259   push        ecx
0040125A   lea         edi,[ebp-44h]
0040125D   mov         ecx,11h
00401262   mov         eax,0CCCCCCCCh
00401267   rep stos    dword ptr [edi]
00401269   pop         ecx
0040126A   mov         dword ptr [ebp-4],ecx
0040126D   mov         eax,dword ptr [ebp+8]
00401270   push        eax
00401271   mov         ecx,dword ptr [ebp-4]
00401274   call        @ILT+15(A::A) (00401014)                                               //调用基类构造函数
00401279   mov         ecx,dword ptr [ebp-4]
0040127C   mov         edx,dword ptr [ebp+0Ch]
0040127F   mov         dword ptr [ecx+8],edx
00401282   mov         eax,dword ptr [ebp-4]                                                        //这里初始化m_b
00401285   mov         dword ptr [eax],offset B::`vftable' (0042f030)                //初始化vptr
30:           cout << "constructor of B" << endl;
0040128B   push        offset @ILT+50(std::endl) (00401037)
00401290   push        offset string "constructor of B" (0042f01c)
00401295   push        offset std::cout (00433ea0)
0040129A   call        @ILT+180(std::operator<<) (004010b9)
0040129F   add         esp,8
004012A2   mov         ecx,eax
004012A4   call        @ILT+135(std::basic_ostream<char,std::char_traits<char> >::operator<<) (0040108c)

A类构造函数汇编码:

004012E0   push        ebp
004012E1   mov         ebp,esp
004012E3   sub         esp,44h
004012E6   push        ebx
004012E7   push        esi
004012E8   push        edi
004012E9   push        ecx
004012EA   lea         edi,[ebp-44h]
004012ED   mov         ecx,11h
004012F2   mov         eax,0CCCCCCCCh
004012F7   rep stos    dword ptr [edi]
004012F9   pop         ecx
004012FA   mov         dword ptr [ebp-4],ecx
004012FD   mov         eax,dword ptr [ebp-4]
00401300   mov         ecx,dword ptr [ebp+8]
00401303   mov         dword ptr [eax+4],ecx
00401306   mov         edx,dword ptr [ebp-4]                                         //初始化m_a
00401309   mov         dword ptr [edx],offset A::`vftable' (0042f050)  //初始化vptr
10:           cout << "constructor of A" << endl;
0040130F   push        offset @ILT+50(std::endl) (00401037)
00401314   push        offset string "constructor of A" (0042f03c)
00401319   push        offset std::cout (00433ea0)
0040131E   call        @ILT+180(std::operator<<) (004010b9)
00401323   add         esp,8
00401326   mov         ecx,eax
00401328   call        @ILT+135(std::basic_ostream<char,std::char_traits<char> >::operator<<) (0040108c)
11:           output();
0040132D   mov         ecx,dword ptr [ebp-4]
00401330   call        @ILT+195(A::output) (004010c8)

这里还可以看出b对象的vptr是被初始化了两次:

先在基类的构造函数前初始化为指向基类虚函数表(vtble)的指针,然后在自身构造函数前初始化为指向自身类vtble的指针。

而且不管哪种情况,vptr都是在自身构造函数体之前初始化。

所以,在A类的构造函数中调用output函数时,b对象尚未构造完成,vptr指向A类的vtble,当然调用A类的output函数。

当执行b.display时,b对象已经构造完成,vptr指向B类的vtble,当然调用B类的display函数。

Ok,这就解释了前面的输出结果。

接着再引用一个《Inside the c++ object model》中的问题:

Is it safe to invoke a virtual function of the class within its constructor's member initialization list?

我相信聪明的你已经知道答案。

http://blog.csdn.net/passion_wu128/article/details/8580306

C++vptr初始化时间的更多相关文章

  1. 为什么要问Servlet的初始化时间

    Servlet的init方法到底是在什么时候调用的? j2ee specification和java doc中有以下说明 如果load-on-startup设置为>=0, 部署的时候就会调用.  ...

  2. Spark延长SparkContext初始化时间

    有些应用中可能希望先在driver上运行一段java单机程序,然后再初始化SparkContext用集群模式操作java程序返回值.从而避免过早建立SparkContext对象分配集群资源,使资源长时 ...

  3. Unity-Animator深入系列---StateMachineBehaviour初始化时间测试

    回到 Animator深入系列总目录 结果和想的有点出入 测试结果: 1.SMB初始化会被调用多次,次数不可控,当Animator组件重复开关则重复初始化. 2.SMB支持构造函数 MyClass p ...

  4. day03_12/13/2016_bean的管理之作用域与初始化时间

    在Spring中,Bean有几种作用域: 1.singleton作用域 当一个bean的作用域设置为singleton,那么Spring IOC容器中只会存在一个共享的bean实例,并且所有对bean ...

  5. 深入探索c++对象模型

    第一章关于对象 c++在布局和存取时间的额外负担主要有virtual引起 virtual function:运行期动态绑定 virtual base class :base class多次出现在派生类 ...

  6. 【Android自学日记】使用DatePicker以及TimePicker显示当前日期和时间

    DatePicker 1.获取一个日历对象: Calendar cal=Calendar.getInstance(); 2.获取当前日期及时间: int year=cal.get(Calendar.Y ...

  7. iOS时间问题

    在iOS开发中,经常会遇到各种各样的时间问题,8小时时差,时间戳,求时间间隔,农历等等.解决办法网上比比皆是,但大多零零散散,很多资料并没有说明其中问题.这里集中总结一下,以便于以后查阅和供大家参考. ...

  8. C#----对时间结构DateTime的使用(时间日期的使用)

    1.其初始化时间是0001--0--0 0:0:0 class Program { DateTime dt; static void Main(string[] args) { Program pp ...

  9. BW标准数据源初始化设置

    在安装了一干补丁和做好了BW与R3的链接之后(此处有BISIS操心,具体事宜不详),我们就可以登录到R3系统看个究竟了. 磨刀不误砍柴工,先检查一下两边系统的补丁: R3端如下, ,貌似我们是19,通 ...

随机推荐

  1. Interaction with the camera or the photo library

    As we said before, we need a delegate to deal with the user interaction with the camera or the photo ...

  2. Android Studio .grade文件知识

    1.位置 2.问题:当AndroidStudio版本不同的时候,导致.gradle文件版本不同(当前为2.10),因为AndroidStudio升级的时候会自动升级.gradle.这样通过svn加载到 ...

  3. Android的Drawable

    1.获取Drawable的内部宽\高:getIntrinsicHeight/Width.但是并不是所有Drawable都有内部宽高(比如说一个颜色形成的宽高,Drawable的宽高不等于大小,大小是根 ...

  4. centos出现磁盘坏掉,怎么修复和检测。

    当dmesg的时候,出现下面的信息说明磁盘有问题 Info fld=0x139066d0 end_request: I/O error, dev sda, sector 328230608 Buffe ...

  5. nginx安装及配置支持php的教程(全)

    本文的实验环境为:Centos4.5,nginx版本为:nginx-0.7.26   pcre-7.8.tar.gz  正则表达式下载地址:ftp://ftp.csx.cam.ac.uk/pub/so ...

  6. android 遇到的细节 FAQ

    1.ListView 设置addHead 在3.0与之前版本若在:setAdapter之后添加,运行报错.4.0以后不报错 2.ListView  Adapter    getView函数忘记返回vi ...

  7. Filter与Servlet的区别和联系

    Filter Servlet 接口 实现Filter接口 实现Servlet接口 使用步骤 1.创建类,继承接口 2.实现方法 init() doFilter() destroy() 3.配置WEB- ...

  8. Qt 鼠标样式特效探索样例(一)——利用时间器调用QWidget.move()函数

    Qt 鼠标样式特效探索样例(一)       心血来潮,突然想在Qt里玩一把鼠标样式,想到在浏览网页时,经常看到漂亮的鼠标动画,于是今天摸索着乱写个粗糙的demo,来满足自己的好奇心. 效果图 方案要 ...

  9. logstash Codec

    Logstash 使用一个名叫FileWatch的Ruby Gem库来监听文件变化,这个库支持glob扩展文件路径, 而且会记录一个叫.sincedb的数据库文件来跟踪被监听日志文件的当前读取位置,所 ...

  10. 定义不同版本的jQuery

    <script type="text/javascript" src="/js/jquery-2.0.3.min.js"></script&g ...