深入理解构造函数中的执行顺序
本文将探讨vbptr、vptr、父类构造、成员对象、初始化列表和构造函数内语句的执行顺序
初始化列表与成员类定义顺序的关系
代码
#include <iostream>
class test1
{
public:
test1()
{
std::cout << "test2" << std::endl;
};
};
class test2
{
public:
test2()
{
std::cout << "test2" << std::endl;
};
};
class Base
{
public:
Base(){}
};
class Drive:public Base
{
public:
Drive():t2(),t1(){}
public:
test1 t1;
test2 t2;
};
int main()
{
Base *drive = new Drive();
return 0;
}
汇编
00843490 push ebp
00843491 mov ebp,esp
00843493 sub esp,0CCh
00843499 push ebx
0084349A push esi
0084349B push edi
0084349C push ecx
0084349D lea edi,[ebp-0CCh]
008434A3 mov ecx,33h
008434A8 mov eax,0CCCCCCCCh
008434AD rep stos dword ptr es:[edi]
008434AF pop ecx
008434B0 mov dword ptr [this],ecx
008434B3 mov ecx,offset _51CD6868_深入理解构造函数中的执行顺序@cpp (08502BBh)
008434B8 call @__CheckForDebuggerJustMyCode@4 (08412DFh)
008434BD mov ecx,dword ptr [this]
008434C0 call Base::Base (08412F8h)
008434C5 mov ecx,dword ptr [this]
008434C8 call test1::test1 (0841032h)
008434CD mov ecx,dword ptr [this]
008434D0 add ecx,1
008434D3 call test2::test2 (084111Dh)
008434D8 mov eax,dword ptr [this]
008434DB pop edi
008434DC pop esi
008434DD pop ebx
分析
- 首先执行了父类构造是没用任何质疑的
- 代码中初始化列表t2是在t1前面的,但是汇编代码执行t1是在t2前面的,这也就是说成员对象的初始化与你初始化列表写的先后顺序无关,与你定义的顺序是由关系的
总结
先执行父类构造,在执行初始化列表,初始化列表在执行顺序与成员变量的定义顺序有关系,与初始化列表中的书写顺序无关
继承关系与初始化列表与构造函数代码块的执行顺序
#include <iostream>
class test1
{
public:
test1()
{
std::cout << "test2" << std::endl;
};
};
class test2
{
public:
test2()
{
std::cout << "test2" << std::endl;
};
};
class Base
{
public:
Base(){}
};
class Drive:public Base
{
public:
Drive():t2()
{
std::cout << "Drive" << std::endl;
}
public:
test1 t1;
test2 t2;
};
int main()
{
Base *drive = new Drive();
return 0;
}
汇编代码:
01003250 push ebp
01003251 mov ebp,esp
01003253 sub esp,0CCh
01003259 push ebx
0100325A push esi
0100325B push edi
0100325C push ecx
0100325D lea edi,[ebp-0CCh]
01003263 mov ecx,33h
01003268 mov eax,0CCCCCCCCh
0100326D rep stos dword ptr es:[edi]
0100326F pop ecx
01003270 mov dword ptr [this],ecx
01003273 mov ecx,offset _51CD6868_深入理解构造函数中的执行顺序@cpp (010102BBh)
01003278 call @__CheckForDebuggerJustMyCode@4 (010012DFh)
0100327D mov ecx,dword ptr [this]
01003280 call Base::Base (010012F8h)
01003285 mov ecx,dword ptr [this]
01003288 call test1::test1 (01001032h)
25: };
26:
27: class Drive:public Base
28: {
29: public:
30: Drive():t2()
0100328D mov ecx,dword ptr [this]
01003290 add ecx,1
01003293 call test2::test2 (0100111Dh)
32: std::cout << "Drive" << std::endl;
01003298 mov esi,esp
0100329A push offset std::endl<char,std::char_traits<char> > (01001316h)
0100329F push offset string "Drive" (0100ACD4h)
010032A4 mov eax,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (0100E0D4h)]
32: std::cout << "Drive" << std::endl;
010032A9 push eax
010032AA call std::operator<<<std::char_traits<char> > (01001262h)
010032AF add esp,8
010032B2 mov ecx,eax
010032B4 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0100E0A0h)]
010032BA cmp esi,esp
010032BC call __RTC_CheckEsp (010012E9h)
33: }
分析
- 在初始化列表并没有把成员对象写全的情况下,会在构造函数执行之前插入一个代码块,这个代码块执行了父类的构造函数和我们没用定义的初始化列表(需要按定义顺序执行),最后在执行构造函数内部的代码块
总结
执行顺序是 父类构造 初始化成员列表 构造函数中的代码块
vptr vbptr 继承 初始化成员列表 和 构造函数代码块
#include <iostream>
class test1
{
public:
test1()
{
std::cout << "test2" << std::endl;
};
};
class test2
{
public:
test2()
{
std::cout << "test2" << std::endl;
};
};
class Base
{
public:
Base() {}
};
class Base2
{
public:
Base2(){}
};
class Drive :public Base, virtual public Base2
{
public:
Drive() :t2(), t1()
{
std::cout << "Drive" << std::endl;
}
virtual void temp(){}
public:
test1 t1;
test2 t2;
};
int main()
{
Base *drive = new Drive();
return 0;
}
汇编代码:
37: {
00DD3510 push ebp
00DD3511 mov ebp,esp
00DD3513 sub esp,0CCh
00DD3519 push ebx
00DD351A push esi
00DD351B push edi
00DD351C push ecx
00DD351D lea edi,[ebp-0CCh]
00DD3523 mov ecx,33h
00DD3528 mov eax,0CCCCCCCCh
00DD352D rep stos dword ptr es:[edi]
00DD352F pop ecx
00DD3530 mov dword ptr [this],ecx
00DD3533 mov ecx,offset _51CD6868_深入理解构造函数中的执行顺序@cpp (0DE02BBh)
00DD3538 call @__CheckForDebuggerJustMyCode@4 (0DD12DFh)
00DD353D cmp dword ptr [ebp+8],0
00DD3541 je Drive::Drive+48h (0DD3558h)
00DD3543 mov eax,dword ptr [this]
00DD3546 mov dword ptr [eax+4],offset Drive::`vbtable' (0DDACDCh)
00DD354D mov ecx,dword ptr [this]
00DD3550 add ecx,0Ch
00DD3553 call Base2::Base2 (0DD14C4h)
00DD3558 mov ecx,dword ptr [this]
00DD355B add ecx,8
00DD355E call Base::Base (0DD12F8h)
00DD3563 mov eax,dword ptr [this]
00DD3566 mov dword ptr [eax],offset Drive::`vftable' (0DDACD8h)
31: };
32:
33: class Drive :public Base, virtual public Base2
34: {
35: public:
36: Drive() :t2(), t1()
00DD356C mov ecx,dword ptr [this]
00DD356F add ecx,8
00DD3572 call test1::test1 (0DD1032h)
00DD3577 mov ecx,dword ptr [this]
00DD357A add ecx,9
00DD357D call test2::test2 (0DD111Dh)
38: std::cout << "Drive" << std::endl;
00DD3582 mov esi,esp
00DD3584 push offset std::endl<char,std::char_traits<char> > (0DD1316h)
00DD3589 push offset string "Drive" (0DDACE4h)
00DD358E mov eax,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (0DDE0D4h)]
00DD3593 push eax
00DD3594 call std::operator<<<std::char_traits<char> > (0DD1262h)
00DD3599 add esp,8
00DD359C mov ecx,eax
00DD359E call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0DDE0A0h)]
00DD35A4 cmp esi,esp
00DD35A6 call __RTC_CheckEsp (0DD12E9h)
39: }
00DD35AB mov eax,dword ptr [this]
00DD35AE pop edi
00DD35AF pop esi
00DD35B0 pop ebx
00DD35B1 add esp,0CCh
00DD35B7 cmp ebp,esp
00DD35B9 call __RTC_CheckEsp (0DD12E9h)
00DD35BE mov esp,ebp
00DD35C0 pop ebp
00DD35C1 ret 4
分析
- 首先执行了父类构造
- 执行了虚继承的虚基类表
- 执行了虚继承的构造函数(注意:这里的虚继承是写在第二个位置)
- 执行第一个父类构造
- 执行虚函表
- 执行初始化列表
- 执行构造函数代码块