Chinaunix首页 | 论坛 | 博客
  • 博客访问: 357414
  • 博文数量: 102
  • 博客积分: 2000
  • 博客等级: 大尉
  • 技术积分: 1116
  • 用 户 组: 普通用户
  • 注册时间: 2007-03-29 16:21
文章分类

全部博文(102)

文章存档

2014年(10)

2011年(1)

2008年(2)

2007年(89)

我的朋友

分类: C/C++

2007-09-08 02:10:39

在继承和多态中,不同子类重写基类中的函数,然后统一由基类的函数接口来调用不同子类的具体函数,在这种情况下,不能使用前期绑定,因为编译器不知道对象的具体类型,只能在运行时动态绑定到具体的类型。在代码中的表现就是使用virtual关键字来修饰要重写的函数,称为虚函数。

如果一个函数在基类中被声明为virtual,那么在所有的派生类中它都是virtual的。

 

那么C++是如何实现后期绑定的呢?

关键字virtual告诉编译器它不应当完成早期绑定,相反,它应当自动安装实现后期绑定所必需的所有机制。为了完成这件事,编译器对每个包含虚函数的类创建一个虚函数表(称为VTABLE)。在VTABLE中,编译器放置特定类的虚函数地址。在每个带有虚函数的类中,编译器秘密地置一指针,称为vpointer(缩写为VPTR),指向这个对象的VTABLE。通过基类指针做虚函数调用时,编译器静态地插入取得这个VPTR,并在VTABLE表中查找函数地址的代码,这样就能调用正确的函数使后期绑定发生。

 

在任何类中,不存在显式的类型信息。而必须有一些信息放在对象中,否则,类型不能在运行时建立。实际上,类型信息被隐藏了。一个只带有一个整型成员的类的长度就是单个int的长度;而在前者的基础上再加上一个或多个虚函数的类的长度等于没有虚函数的长度再加上一个void指针的长度。它反映出,如果有一个或多个虚函数,编译器在这个结构中插入一个指针(VPTR)。如果一个类中没有数据成员,C++编译器会强制这个对象是非零长度,因为每个对象必须有一个相互区别的地址。这时一个哑成员被插入到对象中,否则这个对象就有零长度。当virtual关键字插入类型信息时,这个哑成员的位置就被占用。

 

每当创建一个包含虚函数的类或者从一个有虚函数的基类中派生一个类时,编译器就为这个类创建一个VTABLE,在这个表中,放置了在这个类中或它的基类中所有声明为virtual的虚函数的地址。然后编译器在这个类中放置VPTR指向相应的VTABLEVPTR必须在构造函数中被初始化,在VPTR初始化之前,绝对不能调用虚函数。所有的基类对象或者从基类派生出的对象的VPTR都在各自对象的相同位置。所有的VTABLE有相同的顺序,不管何种类型的对象。

 

C++的函数调用与C一样,都是从右向左进栈的,其间,对象的首地址也即this指针的值被压入栈,正因为调用每个成员函数时this都必须作为参数压进栈,所以成员函数知道它工作在哪个特殊对象上。这样,我们总能看到,在成员函数调用之前压栈的次数等于参数个数加一(除了static成员函数,它没有this)。

 

抽象类就是在类的声明前面加上virtual关键字,为了防止误用抽象类,可以在抽象类中定义纯虚函数,例如virtual void x()=0

这样做,等于告诉编译器在VTABLE中为函数保留一个间隔,但在这个特定间隔不放地址,只要有一个纯虚函数,则VTABLE就是不完全的,包含纯虚函数的类称为纯抽象基类。

   

对象切片的概念

所为对象切片实际上就是类型向上兼容变窄,比如将一个派生类对象赋值给基类对象,那么这种情况下就会产生对象切片,编译器将派生类中对应于基类的那部分成员拷贝到基类对象中,切除这个派生类对象的派生部分,实际上是去掉了对象的一部分。

 

虚机制在构造函数和析构函数中都不工作,调用的都是成员函数的本地版本。在构造函数中是因为信息还不可用,在析构函数中是因为信息(也就是VPTR)虽然存在,但不可靠。

阅读(913) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~