简略来说,编译器会对初始化列表按照成员变量的声明顺序重新一一排序,安插到构造函数中进行初始化操作,而且这些初始化操作在构造函数里面用户自己定义的任何代码之前。

下面是c++源码:

  1. class X {
  2. private:
  3. int i;
  4. int j;
  5. int k;
  6. int l;
  7. public:
  8. X() : j(), i(), l() {
  9. k = ;
  10. }
  11. };
  12.  
  13. int main() {
  14. X x;
  15. }

下面是main函数里面的汇编码:

  1. ; 13 : int main() {
  2.  
  3. push ebp
  4. mov ebp, esp
  5. sub esp, ; 为对象x预留16byte的空间
  6.  
  7. ; 14 : X x;
  8.  
  9. lea ecx, DWORD PTR _x$[ebp];将对象x的首地址传递给寄存器ecx,作为隐含参数传递给构造函数(即this指针)
  10. call ??0X@@QAE@XZ ; 调用构造函数
  11.  
  12. ; 15 : }
  13.  
  14. xor eax, eax
  15. mov esp, ebp
  16. pop ebp
  17. ret
  18. _main ENDP

下面是构造函数的汇编码:

  1. ??0X@@QAE@XZ PROC ; X::X, COMDAT
  2. ; _this$ = ecx
  3.  
  4. ; 8 : X() : j(1), i(2), l(3) {
  5.  
  6. push ebp
  7. mov ebp, esp
  8. push ecx;将寄存器ecx压栈的目的是为保留对象首地址预留空间
  9. mov DWORD PTR _this$[ebp], ecx;将对象首地址存到刚才预留的空间里面
  10. mov eax, DWORD PTR _this$[ebp];将对象首地址给寄存器eax
  11. mov DWORD PTR [eax], ;将2写入对象首地址所指内存,即将2赋给成员变量i
  12. mov ecx, DWORD PTR _this$[ebp];将对象首地址给寄存器ecx
  13. mov DWORD PTR [ecx+], ;将1赋给偏移对象首地址4byte处内存,即将1赋给成员变量j
  14. mov edx, DWORD PTR _this$[ebp];将对象首地址给edx寄存器
  15. mov DWORD PTR [edx+], ;将3写入偏移对象首地址12byte处,即将3赋给成员变量l
  16.  
  17. ; 9 : k = 4;
  18.  
  19. mov eax, DWORD PTR _this$[ebp];将对象首地址给寄存器eax
  20. mov DWORD PTR [eax+], ;将4写入偏移对象首地址8byte处,即将4赋给成员变量k
  21.  
  22. ; 10 : }
  23.  
  24. mov eax, DWORD PTR _this$[ebp]
  25. mov esp, ebp
  26. pop ebp
  27. ret
  28. ??0X@@QAE@XZ ENDP

从汇编吗可以看到,在初始化列表中,虽然j排在i的前面(i的声明在j前),但是,编译器插入到构造函数里面的初始化操作仍然将i的初始化排在了j的前面。并且编译器插入到构造函数里面的初始化操作都在构造函数里面原有初始化操作(初始化k)的前面,尽管l的声明顺序比k的声明顺序晚。

如果不注意声明顺序和初始化列表顺序的这种关系,可能会导致下面的问题:

  1. class X {
  2. private:
  3. int i;
  4. int j;
  5. public:
  6. X() : j(), i(j) {
  7. }
  8. };
  9.  
  10. int main() {
  11. X x;
  12. }

通过上面的分析可以知道,i先初始化,但这时候初始化i的时候j还没初值,所以i的值无法预知。

有4中情况必须使用初始化列表:

1 当初始化一个引用成员时

2 当初始化一个const成员时

3 当调用基类的构造函数,而它拥有参数时

4 当调用成员变量的构造函数,而它拥有参数时

下面来看一种情形:

先来看c++源码:

  1. class X {
  2. private:
  3. int i;
  4. public:
  5. X(int ii) {
  6. i = ii;
  7. }
  8. X() {}
  9. };
  10. class Y {
  11. private:
  12. int j;
  13. X x;
  14. public:
  15. Y() {
  16. j = ;
  17. x = X();
  18. }
  19. };
  20.  
  21. int main() {
  22. Y y;
  23. }

接下来是main函数中的汇编码:

  1. _main PROC
  2.  
  3. ; 21 : int main() {
  4.  
  5. push ebp
  6. mov ebp, esp
  7. sub esp, ;为对象y预留8byte空间
  8.  
  9. ; 22 : Y y;
  10.  
  11. lea ecx, DWORD PTR _y$[ebp];将y对象首地址传递给寄存器ecx,作为隐含参数传递给构造函数
  12. call ??0Y@@QAE@XZ ; 调用对象y的构造函数
  13.  
  14. ; 23 : }
  15.  
  16. xor eax, eax
  17. mov esp, ebp
  18. pop ebp
  19. ret
  20. _main ENDP

下面主要来看对象y的构造函数汇编码:

  1. ??0Y@@QAE@XZ PROC ; Y::Y, COMDAT
  2. ; _this$ = ecx
  3.  
  4. ; 15 : Y() {
  5.  
  6. push ebp
  7. mov ebp, esp
  8. sub esp, ;为保留函数中使用到的变量预留空间,这里主要是this指针和临时对象
  9. mov DWORD PTR _this$[ebp], ecx;将对象y的首地址保留到刚才预留空间
  10. mov ecx, DWORD PTR _this$[ebp];将对象首地址给寄存器ecx
  11. add ecx, ;对象首地址加4,此时ecx中存储的是对象y的成员对象x的首地址,它将作为隐含参数传递给成员对象x的构造函数
  12. call ??0X@@QAE@XZ ; 调用成员对象x的默认构造函数
  13. ;在执行对象y构造函数里面的代码之前,调用成员对象的构造函数
  14. ;这里,如果成员对象x没有默认构造函数,将出错
  15.  
  16. ; 16 : j = 1;
  17.  
  18. mov eax, DWORD PTR _this$[ebp];将对象y首地址给eax寄存器
  19. mov DWORD PTR [eax], ;将1写入y首地址处内存,寄给成员变量j赋值1
  20.  
  21. ; 17 : x = X(2);
  22.  
  23. push ;将2压栈,作为参数传递给成员对象x的构造函数
  24. lea ecx, DWORD PTR $T2568[ebp];将临时对象的首地址给ecx寄存器,作为隐含参数传递给类X的构造函数
  25. call ??0X@@QAE@H@Z ; 调用类X的构造函数,有参数
  26. mov ecx, DWORD PTR [eax];eax寄存器中保留的是临时对象的首地址,这里将临时对象首地址值处内存内容给ecx寄存器,
  27. ;即将临时对象成员变量i值给寄存器ecx
  28. mov edx, DWORD PTR _this$[ebp];将对象y的首地址给edx寄存器
  29. mov DWORD PTR [edx+], ecx;将ecx寄存里面的内容给偏移对象y首地址4byte处内存
  30. ;即将临时对象成员变量值拷贝到成员对象x
  31. ;这相当于(operate=的拷贝功能)
  32.  
  33. ; 18 : }
  34.  
  35. mov eax, DWORD PTR _this$[ebp]
  36. mov esp, ebp
  37. pop ebp
  38. ret
  39. ??0Y@@QAE@XZ ENDP

可以看到,在对象y的构造函数里面,在执行对象y的构造函数里面任何赋值操作之前,首先调用了成员对象x的默认构造函数,接下来产生了一个临时对象,然后将临时对象在拷贝给成员对象x。这里可以看到,初始化成员对象x的操作是调用默认构造函数来完成的,对象y的构造函数里面的赋值操作只是一个拷贝过程。

下面将成员对象的初始化放到初始化列表中,并且去掉其默认构造函数

先看c++源码

  1. class X {
  2. private:
  3. int i;
  4. public:
  5. X(int ii) {
  6. i = ii;
  7. }
  8.  
  9. };
  10. class Y {
  11. private:
  12. int j;
  13. X x;
  14. public:
  15. Y() : x() {
  16. j = ;
  17. }
  18. };
  19.  
  20. int main() {
  21. Y y;
  22. }

下面看main函数汇编码:

  1. _main PROC
  2.  
  3. ; 20 : int main() {
  4.  
  5. push ebp
  6. mov ebp, esp
  7. sub esp, ;为对象y预留8byte空间
  8.  
  9. ; 21 : Y y;
  10.  
  11. lea ecx, DWORD PTR _y$[ebp];将对象y的首地址给ecx寄存器,作为隐含参数传递给构造函数
  12. call ??0Y@@QAE@XZ ; 调用对象y的构造函数
  13.  
  14. ; 22 : }
  15.  
  16. xor eax, eax
  17. mov esp, ebp
  18. pop ebp
  19. ret
  20. _main ENDP

下面主要看对象y的构造函数汇编码:

  1. ??0Y@@QAE@XZ PROC ; Y::Y, COMDAT
  2. ; _this$ = ecx
  3.  
  4. ; 15 : Y() : x(2) {
  5.  
  6. push ebp
  7. mov ebp, esp
  8. push ecx;压栈的目的是为了保留对象y的首地址预留空间
  9. mov DWORD PTR _this$[ebp], ecx;将对象y的首地址存到刚才预留的空间
  10. push ;将2压栈,作为参数传递给成员对象x的构造函数
  11. mov ecx, DWORD PTR _this$[ebp];将对象y的首地址给ecx寄存器
  12. add ecx, ;将对象y的首地址加4,得到的是成员对象x的首地址,作为隐含参数传递给成员对象x的构造函数
  13. call ??0X@@QAE@H@Z ; 调用x的构造函数,有参数
  14.  
  15. ; 16 : j = 1;
  16.  
  17. mov eax, DWORD PTR _this$[ebp];将对象y的首地址给寄存器eax
  18. mov DWORD PTR [eax], ;将1写入对象y首地址处内存,即将1赋给对象y的成员变量j
  19.  
  20. ; 17 : }
  21.  
  22. mov eax, DWORD PTR _this$[ebp]
  23. mov esp, ebp
  24. pop ebp
  25. ret
  26. ??0Y@@QAE@XZ ENDP

可以看到,和上面的最大不同是,即使类X没有默认构造函数,编译器也没有报错,并且,初始化成员对象的操作由有参数的构造函数完成,当中并没有产生临时对象,效率高。

从汇编看c++初始化列表初始化成员变量的更多相关文章

  1. Effective C++学习笔记:初始化列表中成员列出的顺序和它们在类中声明的顺序相同

    类成员的默认初始化顺序是按照声明顺序进行, 如果使用初始化列表初始化成员变量, 则必须按照成员变量的声明顺序进行; 否则, 在变量之间交替赋值时, 会产生, 未初始化的变量去赋值其他变量; 同时GCC ...

  2. 【c++】构造函数初始化列表中成员初始化的次序性

    上代码 #include <iostream> using namespace std; class A { public: A(int v): j(v + 2), i(j) {} voi ...

  3. C++:只用初始化列表初始化变量的几种情况

    1.类成员函数中const变量的初始化(也就是第一点) 有几个容易混淆的地方: (1)const 的变量只能通过构造函数的初始化列表进行初始化:(貌似在C++11中可以正常编译) (2)static ...

  4. 默认初始化&拷贝初始化&直接初始化&值初始化&列表初始化

    一.各种初始化的形式 /* 定义变量形式一:不指定初始值 */ int a; // 默认初始化 /* 定义变量形式二:指定初始值 */ int b = 1; // 拷贝初始化 int b(1); // ...

  5. java初始化过程中成员变量

    package day01; class Base{ int j; //1.j=0 Base(){ add(1); //2.调用子类add()方法 System.out.println(j); //4 ...

  6. c++ 初始化静态static成员变量或static复合成员变量

    https://stackoverflow.com/questions/185844/how-to-initialize-private-static-members-in-c https://sta ...

  7. C++中初始化列表的使用(总结)

    原文链接 https://www.cnblogs.com/dishengAndziyu/p/10906081.html 参考链接:https://www.cnblogs.com/laiqun/p/57 ...

  8. c++——初始化列表

    多个对象构造和析构 1对象初始化列表 1)对象初始化列表出现原因 1.必须这样做: 如果我们有一个类成员,它本身是一个类或者是一个结构,而且这个成员它只有一个带参数的构造函数,没有默认构造函数.这时要 ...

  9. C++解析(12):初始化列表与对象构造顺序、析构顺序

    0.目录 1.类成员的初始化 2.类中的const成员 3.对象的构造顺序 3.1 局部对象的构造顺序 3.2 堆对象的构造顺序 3.3 全局对象的构造顺序 4.对象的析构顺序 5.小结 1.类成员的 ...

随机推荐

  1. 原创+部分引用啦:C# Winform界面中的分隔线问题

    C sharp 中Winform中的控件很多,一个小小的问题居然会绕上一个小弯子,做界面的时候, 你需要在界面上弄一条分隔线,把相关的功能分隔开来,结果原来在其它 IDE编辑器里很容易实现的这个功能, ...

  2. Comparator TreeSet

    package study; import java.util.Comparator;import java.util.TreeSet; public class TreeSetTest { publ ...

  3. (原)编译caffe时提示未定义的引用(undefined reference to)

    转载请注明出处: http://www.cnblogs.com/darkknightzh/p/5864715.html 参考网址: https://github.com/BVLC/caffe/issu ...

  4. 高手总结的CSS执行顺序及其优先权问题汇总

    今天在看一本书时又看到了”CSS优 先权“这个问题,感觉这个问题还是比较重要的,也算是样式的特异性吧,尤其是在面对较多.较深层.较复杂的样式属性时,理解CSS的加权计算方法对于重写 样式属性之类的问题 ...

  5. javascrit字符串截取

    昨天遇见一个问题就是一个地址后面加参数第一次是需要添加参数,以后每次点击按钮的时候是替换如果不进行处理的话如果页面不刷新,地址会不断的添加越来越长,所以

  6. 火星A+B..(不贴代码了)

    还是A+B Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Submi ...

  7. mysql sql_mode 之 NO_ENGINE_SUBSTITUTION

    知识储备: 1.mysql 有众多的存储引擎,然而只有一个默认的存储引擎,通常来说它是innodb 2.mysql 可以通过sql_mode 来控制mysql 数据库的行为,今天我们要讲的就是no_e ...

  8. js阻止事件冒泡的方法

    /********************************************js方法*************************************************** ...

  9. QSizePolicy可均匀调整控件的大小,还可设置比例,非常完美(每个QWidget都有这个功能)

    http://blog.csdn.net/liang19890820/article/details/51986284 它是QWidget的固有属性: http://doc.qt.io/qt-4.8/ ...

  10. Qt浅谈之二十App自动重启及关闭子窗口(六种方法)

    一.简介 最近因项目需求,Qt程序一旦检测到错误,要重新启动,自己是每次关闭主窗口的所有子窗口但有些模态框会出现问题,因此从网上总结了一些知识点,以备以后的应用. 二.详解 1.Qt结构 int ma ...