C中构造函数中调用虚函数问题.docVIP

  • 5
  • 0
  • 约 5页
  • 2017-04-12 发布于四川
  • 举报
C中构造函数中调用虚函数问题

C++中构造函数中调用虚函数的问题 在构造函数中调用虚成员函数,虽然这是个不很常用的技术,但研究一下可以加深对虚函数机制及对象构造过程的理解。这个问题也和一般直观上的认识有所差异。先看看下面的两个类定义。 struct C180 {  C180() {   foo();   this-foo();  }  virtual foo() {   cout C180.foo this: this vtadr: *(void**)this endl;  } }; struct C190 : public C180 {  C190() {}  virtual foo() {   cout C190.foo this: this vtadr: *(void**)this endl;  } };   父类中有一个虚函数,并且父类在它的构造函数中调用了这个虚函数,调用时它采用了两种方法一种是直接调用,一种是通过this指针调用。同时子类又重写了这个虚函数。   我们可以来预测一下如果构造一个C190的对象会发生什么情况。   我们知道,在构造一个对象时,过程是这样的: 1) 首先会按对象的大小得到一块内存(在heap上或在stack上), 2) 把指向这块内存的指针做为this指针来调用类的构造函数,对这块内存进行初始化。 3) 如果对象有父类就会先调用父类的构造函数(并依次递归),如果有多个父类(多重继承)会依次对父类的构造函数进行调用,并会适当的调整this指针的位置。在调用完所有的父类的构造函数后,再执行自己的代码。   照上面的分析构造C190时也会调用C180的构造函数,这时在C180构造函数中的第一个foo调用为静态绑定,会调用到C180::foo()函数。第二个foo调用是通过指针调用的,这时多态行为会发生,应该调用的是C190::foo()函数。   执行如下代码: C190 obj; obj.foo();   结果为: C180.foo this: 0012F7A4 vtadr: 0045C404 C180.foo this: 0012F7A4 vtadr: 0045C404 C190.foo this: 0012F7A4 vtadr: 0045C400 和我们的分析大相径庭。第一行是在C180中运行foo()函数得到的,这里的foo()当然是调用C180中的foo()函数。第二行是调用C180中的this-foo()得到的,此时this指向的应该是C180的虚表地址,按照调用规则,应该是动态绑定,即,此时若派生类对该虚函数实现过,则应该调用派生类的虚函数,这里是一个例外,下面会详细讲到。 至此,C190的父类的构造函数运行完毕,转而运行C190的构造函数,但是这里C190的构造函数什么都没有。第三行是在main函数中调用obj.foo()得到的,这里直接进入C190运行就可以了。 这里必须注意一点,就是前两行和第三行的虚表是不同的,这是因为前两行的虚表是C180的虚表,而第三行的虚表是C190的虚表。 其实这正是奥秘所在。   为此我查了一下C++标准规范。在12.7.3条中有明确的规定。这是一种特例,在这种情况下,即在构造子类时调用父类的构造函数,而父类的构造函数中又调用了虚成员函数,这个虚成员函数即使被子类重写,也不允许发生多态的行为。即,这时必须要调用父类的虚函数,而不子类重写后的虚函数。   我想这样做的原因是因为在调用父类的构造函数时,对象中属于子类部分的成员变量是肯定还没有初始化的,因为子类构造函数中的代码还没有被执行。如果这时允许多态的行为,即通过父类的构造函数调用到了子类的虚函数,而这个虚函数要访问属于子类的数据成员时就有可能出错。   我们看看VC7.1生成的汇编代码就可以很容易的理解这个行为了。 这是C190的构造函数: 01 00426FE0 push ebp 02 00426FE1 mov ebp,esp 03 00426FE3 sub esp,0CCh 04 00426FE9 push ebx 05 00426FEA push esi 06 00426FEB push edi 07 00426FEC push ecx 08 00426FED lea edi,[ebp+FFFFFF34h] 09 00426FF3 mov ecx,33h 10 00426FF8 mov eax,0CCCCCCCCh 11 00426FFD rep stos dword ptr [edi] 12 00426FFF pop ecx 13 0042

您可能关注的文档

文档评论(0)

1亿VIP精品文档

相关文档