虚函数的实现原理问题

digu 2007-11-19 04:07:55
我的理解:
每当创建一个包含有虚函数的类或从包含有虚函数的类派生一个类时,编译器就为这个类创建一个虚函数表(VTABLE)保存该类所有虚拟函数的地址, 当构造该派生类对象时,其成员VPTR被初始化指向该派生类的VTABLE。所以可以认为VTABLE是该类的所以对象共有的,在定义该类是被初始化;而VPTR则是每个类对象都有独立一份的,且在该类对象被构造时被初始化。

然后我们就可以很容易的解释为什么动态绑定不能使用类对象(通过基类对象调用派生类实现虚拟函数),因为该基类对象在构造时其VPTR就指向了基类的VTABLE;而使用基类的指针或者引用,如果认为该类构造是发生在调用该类虚拟函数时也是不正确的,下面这段测试代码证明了这一点:
#include <iostream>
using namespace std;

class base
{
public:
void bfun(){cout<<"call base"<<endl;}
virtual void vfun1(){cout<<"base"<<endl;}
private:
int a;
};

class derived : public base
{
public:
virtual void vfun1(){cout<<"derived"<<endl;}
private:
int b;
};

void display(base *pbase)
{
pbase->bfun();
pbase->vfun1();
}

int main()
{
derived *pb = new derived;
display(pb);
return 0;
}
输出:call base
derived

我现在最大的问题:对于派生类对象的构造(VPTR的初始化)是在什么时候发生的,因为派生类对象VPTR一定要指向派生类的VTABLE。

《C++编程思想》里面说“通过基类指针做虚函数调用时(也就是做多态调用时),编译器静态地插入取得这个V P T R,并在V TA B L E表中查找函数地址的代码,这样就能调用正确的函数使晚捆绑发生。”,我个人的感觉是这里有点问题——编译器静态的取得的这个VPTR是基类的VPTR还是派生类的VPTR,如果是基类的VPTR,那么又如何调用派生类实现的虚拟函数,而如果是派生的VPTR,那么这里编译器又如何可能静态的通过基类指针取得派生类的VPTR呢?

...全文
615 11 打赏 收藏 转发到动态 举报
写回复
用AI写文章
11 条回复
切换为时间正序
请发表友善的回复…
发表回复
loops 2007-11-19
  • 打赏
  • 举报
回复
要搞清这些问题,一看C++标准,二看汇编语言,就会很快明白了。
标准是理论,编译器生成的汇编是实践,如此而已。
不过我基本上都是看汇编,因为理论看起来头疼,还记不住,比不得编译器生成的汇编码那么平易近人。
digu 2007-11-19
  • 打赏
  • 举报
回复
我错了,进入死胡同了,这里根本没有新的派生类临时对象的产生,而是通过指针来访问外部的派生类对象的VPTR来达到访问派生类虚函数的结果。
感谢2位的指点。
ckt 2007-11-19
  • 打赏
  • 举报
回复
看个人理解了,我是觉得那是基础
loops 2007-11-19
  • 打赏
  • 举报
回复
不可以。多态是由对象本身的特性决定的。就是说当你庄严的写下virtual的时候,对象的布局已经决定了。
如果照你这么说,每回有基类指针指向派生类对象的时候,多态才发生,那末C++的效率要有多差啊。现在一个多态已经使得C++的效率比C差了好多了。
digu 2007-11-19
  • 打赏
  • 举报
回复
pbase这里是基类指针指向的派生类对象,是不是可以认为真正的多态是发生在这一步,用基类的指针构造出了派生类的对象?
ckt 2007-11-19
  • 打赏
  • 举报
回复
派生类从基类继承过来的
并不是virtual,就没多态可言
编译器可以直接调用,减少通过vptr寻址的开销
loops 2007-11-19
  • 打赏
  • 举报
回复
可以解释一下为什么pbase-> bfun();(调用基类的非虚拟函数)所构造的对象是派生类对象,而不是基类对象么?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
这个规则跟普通对象指针调用自己的普通方法是一摸一样啊。编译程序直接看看pBase的类的定义就可以生成汇编,而且函数地址一次性就可以得到。

digu 2007-11-19
  • 打赏
  • 举报
回复
可以解释一下为什么pbase-> bfun();(调用基类的非虚拟函数)所构造的对象是派生类对象,而不是基类对象么?
loops 2007-11-19
  • 打赏
  • 举报
回复
vc下讨论单继承
从二进制的视野来看,所谓基类子类是一个大结构体,其中this指针开头的四个字节存放虚函数表头指针。
执行子类的构造函数的时候,首先调用基类构造函数,this指针作为参数,在基类构造函数中填入基类的vptr,然后回到子类的构造函数,填入子类的vptr,覆盖基类填入的vptr。如此以来完成vptr的初始化。
至于动态绑定的时候,很简单,不管子类基类的指针,在调用虚函数的时候,编译器编译的代码里面不管三七二十一,都是先访问this头取得vptr,然后在虚函数表中偏移若干个字节,得到实际的函数的地址,再调用。

ckt 2007-11-19
  • 打赏
  • 举报
回复
对于派生类对象的构造(VPTR的初始化)是在什么时候发生的,因为派生类对象VPTR一定要指向派生类的VTABLE。
--------------
进入派生类的构造函数体之前会其vptr初始化,使其指向该类对应的vtable


我个人的感觉是这里有点问题——编译器静态的取得的这个VPTR是基类的VPTR还是派生类的VPTR
-------------
如果一个类有虚函数,就会为这个类添加数据成员vptr,
当你把一个基类指针指向派生类时,其vptr的当然是派生类的vptr

loops 2007-11-19
  • 打赏
  • 举报
回复
vptr是在子类构造函数调用基类的构造函数之后被初始化填入地址的。

64,637

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧