[中级扫盲贴]关于虚函数表vtable
时间:2010-06-28
来源:互联网
先举一个关于多态的示例程序,观察多态的效果
- class Base {
- public:
- // 注:用virtual修饰
- virtual void who_am_i(void) {
- std::cout<<"I am Base."<<std::endl;
- };
- };
-
- class Derived {
- public:
- void who_am_i(void) {
- std::cout<<"I am Derived."<<std::endl;
- };
- };
-
- int main(void) {
- Derived obj;
- Base *ptr = &obj;
- ptr->who_am_i();
- };
I am Derived.
可是,如果Base::who_am_i 取消 virtual修饰,运行结果就会变成
I am Base.
这是因为vtable的作用,你可以大致上这样理解,实际情况因应不同编译器而略有差异:
1. 首先,父类中所有被 virtual 修饰的成员方法,都会被编译器关联到一个特定数组的下标值,这个值无需程序员关注
譬如 Base::who_am_i() 和 Derived::who_am_i 都会被赋予 4
如果还有一个 Base::foo 和 Derived::foo 都会被赋予 5
如果 Derived 增加了一个新的virtual bar 方法,而父类没有,那么 Derived::bar 以及在其子类中 会被赋予 更大的下标值
2. 所有Base 和 Derived,以及其后的更多的子类的对象的内存布局中,会被额外安插多一个指针项
这个指针项就是指向所谓的 vtable,你可以理解为一个数组(只是可以这样理解,实际情况请自己动手做实验)
然后 vtable 中就会存放各个 virtual 成员方法的入口地址
上述的 who_am_i 方法的入口地址,无论是 Base 的 vtable 还是 Derived 的 vtable,总是被放在下标元素为4 的数组元素中。
尽管 Base::who_am_i 和 Derived::who_am_i 的入口地址是不一样的(所以其实每个类的vtable都是不一样的),
但是在各自的类中,都可以用 vtable[4]取到他们各自的入口地址。
3. 剩下来的事情就很简单了,
将所有对 virtual 方法的调用,由原来的直接 jump 到一个确定的地址
全部改为 jump 到 vtable[n] 的调用,
至于使用哪一个vtable,由指针或引用所指的对象的 vtable 指向所决定,也就是实际类型的 vtable
所以实际情况是这样的,在有virtual 修饰的情况下, ptr->who_am_i(); 这句
会变成类似于: (*ptr->vtable[4])() 这样的代码。
作者: xyfree 发布时间: 2010-06-28
作者: starwing83 发布时间: 2010-06-28
回复 starwing83
不是的,有些时候你的private的方法不会想用virtual的,这样子类就不会无意中修改了理想的行为。
所以Java 里面默认 全部 virtual 是一个很笨的 idea,因为其实 virtual 的成员方法会污染子类的成员方法的命名空间。
不过Java自身也意料到这个问题,所以他们有一个很搞笑的解救办法。
- // java code
-
- // Base.java
- public class Base {
-
- // 注意,这里的private 改成 public 的效果完全不一样
- private void print() {
- System.out.println("I am Base");
- }
- public void who_am_i() {
- print();
- }
- }
-
- // Derived.java
- public class Derived extends Base {
- private void print() {
- System.out.println("I am Derived");
- }
- }
-
- // Test.java
- class Test {
- static public void main(String args[]) {
- Base obj = new Derived();
- obj.who_am_i();
- }
- }
作者: xyfree 发布时间: 2010-06-28
作者: 没本 发布时间: 2010-06-28
作者: peijue 发布时间: 2010-06-28
是啊,可是有人就看不进去怎么办,我觉得我也不会说得比那本书好……虽说那里比较长篇,郁闷。
等这帖沉了之后,又会有更多新手重复问这些问题。
社会进步就是这么慢呐.........
作者: xyfree 发布时间: 2010-06-28
呵呵是啊。我的办法就是看到问的就回点,慢慢赚分。。。。。。
作者: 没本 发布时间: 2010-06-28
作者: 没本 发布时间: 2010-06-28
我认为这些可能多数人都能看得懂
他们的问题恐怕是看不出这些东西如何才能恰倒好处地应用并且强于C
作者: pmerofc 发布时间: 2010-06-28
不是的,有些时候你的private的方法不会想用virtual的,这样子类就不会无意中修 ...
xyfree 发表于 2010-06-28 21:25
你真是……………………
第一,我说的是在一条继承树枝上面,谁跟你扯单个类了?
第二,你的代码有误,class Derived : public Base
第三,0x出来前拒绝讨论C++,光那个复制语义的stl就够呛。
作者: starwing83 发布时间: 2010-06-28
starwing83 发表于 2010-06-28 21:15
我以为你说,一个类里面所有的方法,要么都virtual,要么都别virtual……
谢谢提醒,已改正。
你也写错了, java里面的继承用 关键字 extends 哈哈哈哈
作者: xyfree 发布时间: 2010-06-28
- (*ptr->vtable[4])()

作者: OwnWaterloo 发布时间: 2010-06-28
这个.....没错吧?错了也不管了,反正意思表达到就算了。
作者: xyfree 发布时间: 2010-06-28
我写一个更generic的, 不可能参数全为空吧?
- (int* (*)(int))(p->vtable[0])( 1212 );
- (void (*)(double))(p->vtable[0])( 3.26 );
我猜你也是从汇编看出来的吧? 我也被汇编骗过……
其实用"函数指针的结构体" 更容易表达。
最终生成的代码是一样的, 是按offset寻址。
因为所有函数指针的表示相同, 所以从汇编上看, 就像一个数组。
但用结构体有更好的静态类型检测, 而且书写没这么麻烦……
作者: OwnWaterloo 发布时间: 2010-06-28
你的写法非常精确。除了
一 你都漏了this参数
二 那些方法除了this之外确实是无其他参数的,也不返回,故意的,就因为我知道展开会非常麻烦
我是实在不想在这中情况下考虑类型系统,
只会给思考带来羁绊......哈哈哈哈
反正是编译器替我写那些代码
我就不管了,哈哈

作者: xyfree 发布时间: 2010-06-28
this是从你那里开始漏的……
我就是在给你介绍一种展开不麻烦的方法。
作者: OwnWaterloo 发布时间: 2010-06-29
我大致了解你的意思,你的想法是不是说,将整个函数参数列表视作一个结构体,然后用这个结构体的指针取代原来的参数列表?
那我是不是还要先交待我这样的想法呢?毕竟这不是顺其自然的事情。
所以,与其引入更多的变换信息,不如还是用最传神的表达方式简化掉算了。
这个题外话。
话说,你们那个VimE,写到哪个部分有可能会接纳C++ >_<
作者: xyfree 发布时间: 2010-06-29
- typedef struct I_ I;
- struct I_ {
- int (*f1)(I** self, int p1);
- void (*f2)(I** self, double p1, int p2);
- ...
- };
-
- typedef struct {
- I* vptr_;
- ...
- } C;
-
- int C_f1(I** self, int p1);
- int C_f2(I** self, double p1, int p2);
-
- I C_vtbl = { &C_f1, &C_f2, ... };
-
- C c;
- I** i = &c.vptr_;
-
- (*i)->f1(i, a1);
- (*i)->f2(i, a1, a2);
作者: OwnWaterloo 发布时间: 2010-06-29
vime由sw负责~_~ 直接向他询问~
作者: OwnWaterloo 发布时间: 2010-06-29
热门阅读
-
office 2019专业增强版最新2021版激活秘钥/序列号/激活码推荐 附激活工具
阅读:74
-
如何安装mysql8.0
阅读:31
-
Word快速设置标题样式步骤详解
阅读:28
-
20+道必知必会的Vue面试题(附答案解析)
阅读:37
-
HTML如何制作表单
阅读:22
-
百词斩可以改天数吗?当然可以,4个步骤轻松修改天数!
阅读:31
-
ET文件格式和XLS格式文件之间如何转化?
阅读:24
-
react和vue的区别及优缺点是什么
阅读:121
-
支付宝人脸识别如何关闭?
阅读:21
-
腾讯微云怎么修改照片或视频备份路径?
阅读:28