很简单的测试代码:
class A { public: A() : m_a(111) { bar(); this->bar(); foo(); } virtual void bar() { std::cout << "A::bar" << std::endl; } void foo() { bar(); } int m_a; }; class B : public A { public: B() : m_b(111) { bar(); this->bar(); foo(); } virtual void bar() { std::cout << "B::bar" << std::endl; } int m_b; }; int _tmain(int argc, _TCHAR* argv[]) { B b; return 0; }输出跟预想的一样:
A::bar A::bar A::bar B::bar B::bar B::bar看一下在调用B::B()时编译器产生的代码在干啥:
B() : m_b(111) { 01251590 push ebp 01251591 mov ebp,esp 01251593 sub esp,0CCh 01251599 push ebx 0125159A push esi 0125159B push edi 0125159C push ecx 0125159D lea edi,[ebp-0CCh] 012515A3 mov ecx,33h 012515A8 mov eax,0CCCCCCCCh 012515AD rep stos dword ptr es:[edi] 012515AF pop ecx 012515B0 mov dword ptr [ebp-8],ecx 012515B3 mov ecx,dword ptr [this] 012515B6 call A::A (1251140h) 012515BB mov eax,dword ptr [this] 012515BE mov dword ptr [eax],offset B::`vftable' (1257804h) /// 在调用基类构造函数A::A()之后,这条指令将vptr指向B的vtable 012515C4 mov eax,dword ptr [this] 012515C7 mov dword ptr [eax+8],6Fh /// <- 这里将m_b初始化为111 bar(); 012515CE mov ecx,dword ptr [this] 012515D1 call B::bar (12511E5h) this->bar(); 012515D6 mov ecx,dword ptr [this] 012515D9 call B::bar (12511E5h) foo(); 012515DE mov ecx,dword ptr [this] 012515E1 call A::foo (125128Fh) }注意上面红色的部分,它们告诉我们:
1)在编译构造函数时,编译器会自动加入指令将vptr指向当前类的vtable,并且这发生在由构造函数产生的任何指令之前; 2)如果在构造函数里调用虚函数,编译器会按照调用普通函数来处理,在这里并没有使用vptr;那么再看一下A::A()在干啥:A() : m_a(111) { 003C1BC0 push ebp 003C1BC1 mov ebp,esp 003C1BC3 sub esp,0CCh 003C1BC9 push ebx 003C1BCA push esi 003C1BCB push edi 003C1BCC push ecx 003C1BCD lea edi,[ebp-0CCh] 003C1BD3 mov ecx,33h 003C1BD8 mov eax,0CCCCCCCCh 003C1BDD rep stos dword ptr es:[edi] 003C1BDF pop ecx 003C1BE0 mov dword ptr [ebp-8],ecx 003C1BE3 mov eax,dword ptr [this] 003C1BE6 mov dword ptr [eax],offset A::`vftable' (3C7810h) 003C1BEC mov eax,dword ptr [this] 003C1BEF mov dword ptr [eax+4],6Fh /// <- 这里将m_a初始化为111 bar(); 003C1BF6 mov ecx,dword ptr [this] 003C1BF9 call A::bar (3C11EAh) this->bar(); 003C1BFE mov ecx,dword ptr [this] 003C1C01 call A::bar (3C11EAh) foo(); 003C1C06 mov ecx,dword ptr [this] 003C1C09 call A::foo (3C128Fh) }红色部分再次证实了编译器会在构造函数最前面插入指令将vptr指向当前类的vtable。有没有注意我们还在A的构造函数里调用了一个非虚函数foo(),这个foo()里调用了虚函数bar()?那么究竟哪个bar()会被调用呢?void foo() { 003C16E0 push ebp 003C16E1 mov ebp,esp 003C16E3 sub esp,0CCh 003C16E9 push ebx 003C16EA push esi 003C16EB push edi 003C16EC push ecx 003C16ED lea edi,[ebp-0CCh] 003C16F3 mov ecx,33h 003C16F8 mov eax,0CCCCCCCCh 003C16FD rep stos dword ptr es:[edi] 003C16FF pop ecx 003C1700 mov dword ptr [ebp-8],ecx bar(); 003C1703 mov eax,dword ptr [this] 003C1706 mov edx,dword ptr [eax] 003C1708 mov esi,esp 003C170A mov ecx,dword ptr [this] 003C170D mov eax,dword ptr [edx] 003C170F call eax 003C1711 cmp esi,esp 003C1713 call @ILT+455(__RTC_CheckEsp) (3C11CCh) }显然这里用到了vptr,也就是说会调用vptr当前指向的vtable中的函数。既然foo()是在A的构造函数里调用的,这个时候vptr指向A::vtable,所以A::bar()会被调用。 以上代码在VS2008里测试。