C++vptr初始化时间

给出如下代码段:
#include <iostream>
#include "stdio.h"
using namespace std;

class A
{
public:
	A(int arg):m_a(arg)
	{
		cout << "constructor of A" << endl;
		output();
	}
	virtual void output()
	{ 
		cout << "output A" << endl;
	}
	virtual void display()
	{ 
		output(); 
	}
private:
	int m_a;
};

class B : public A
{
public:
	B(int arg1, int arg2):A(arg1), m_b(arg2)
	{ 
		cout << "constructor of B" << endl;
	}
	virtual void output()
	{ 
		cout << "output B" << endl;
	}
private:
	int m_b;
};


int main(int argc, char* argv[])
{
	B b(1, 2);
	b.display();
	return 0;
}

这段代码会输出什么?

其实这个问题本质是在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?

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

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页