大宝(sodme)的专栏

专注于高性能网络服务器技术研发,关注网游产品设计、研发、市场与运营的各个环节

谢廷宝ID:sodme
328630次访问,排名158好友46人,关注者69
倡导并实践着:开发者应具备团队观和大产品观,崇尚实效至上的开发观,不唯技术论,不唯权威论,不畏惧任何困难,不丢下任一战友。
sodme的文章
原创 117 篇
翻译 0 篇
转载 22 篇
评论 198 篇
大宝(sodme)的公告
我的网易博客:
http://sodme.dev.blog.163.com
GT&mail: sodme.dev#gmail.com
msn: sod_me#hotmail.com

我们的理念:
以市场作产品,以产品作技术。

1. 积极面对自己的工作和生活,是一种有百利而无一害的态度;

2. 不唯技术论,可以让你站在更高的层面去思考更多全局性的东西,而这些东西更直接地影响着产品成败和你个人的发展;

3. 我们希望的是,做一款成功的产品,而不仅仅是做一项成功的技术,如果技术与产品可以合起来当然更好,如果不能,那明智的选择当然是产品;

4. 知识型工作者,要学会自我管理,没有了自我管理,知识型工人可能连一个搬砖工人的价值都顶不上。

最近评论
zatman:谢谢,受益匪浅哈~~~
supperwfy:google的网络框架服务是其精髓。
fjfqslg:看了你的文章受益匪浅,谢谢你,^_^
kissnsms:众所周知,CreateIoCompletionPort函数,有两个作用,一是“创建”一个完成端口,二是将一个socket与已经创建的完成端口句柄相“绑定”,绑定之后,基于该socket的收、发、断开等事件都可以被完成端口感知。

请教一下,如何感知断开?在不进行数据的收发的情况下,谢谢!
rjchen:同意"持续更新, 持续开发, 持续改善能力"的观点,我们现在也在朝这个方向努力,由一个月发布一个新版本,到争取半个月发布一个新版本,到一周发布一个新版本,通过来提高用户的响应速度和体验。
文章分类
收藏
相册
_我的其它站
My GooglePages
My Maillist
My MsnSpace
我的网易博客
非技术
TechWeb
东方宽频
中国企业史
叶蓉
吴晓波
圈子-圈套
士兵突击
多玩网
李书文
王冉
蓝狮子
赢在中国
郎咸平
马未都
存档
订阅我的博客
XML聚合  FeedSky

原创 孔乙己之五----虚函数(下)收藏

新一篇: 小品: 关于"C++引用" | 旧一篇: 由一篇BLOG看多态

本文作者:sodme 本文出处:http://blog.csdn.net/sodme 声明: 本文可以不经作者同意, 任意复制, 转载, 但任何对本文的引用都请保留文章开始前三行的作者, 出处以及声明信息. 谢谢. 本文所需代码可从以下地址获得( 此地址含有多继承c++和asm代码 ): http://docs.google.com/Doc?id=dcb4rbgm_51cqf52xff   前文中, 我们知道了单继承时的虚函数处理方法, 那么, 多继承时, 又是如何处理的呢? 我们仍然重点考察以下几个方面: 1. 虚函数表的变化; 2. 构造函数的变化; 3. 虚函数调用代码的变化.   三个类的继承关系如图:                   |----> Base1Class MyClass |         |----> Base2Class     类MyClass的虚函数表:
    .long    0     .long    _ZTI7MyClass     .long    _ZN7MyClass14virtual_test_1Ev     .long    _ZN10Base1Class14virtual_test_3Ev     .long    _ZN7MyClass15virtual_test_myEv     .long    -8     .long    _ZTI7MyClass     .long    _ZN10Base2Class14virtual_test_2Ev     .long    _ZN10Base2Class14virtual_test_3Ev 
  咦? 从形式上看, 与单继承时确实有所不同, 整个虚函数表似乎被分成了两个部分: 第一部分是Base1Class的, 第二部分是Base2Class的. 那个中幺机何在呢? 别急, 慢慢看.   类MyClass的构造函数:  
    movl    8(%ebp), %eax     movl    %eax, (%esp)     call    _ZN10Base1ClassC2Ev     ;调用Base1Class的构造函数     movl    8(%ebp), %eax     addl    $8%eax     movl    %eax, (%esp)     call    _ZN10Base2ClassC2Ev     ;调用Base2Class的构造函数, 注意其传递的this指针     movl    $_ZTV7MyClass+8%edx     movl    8(%ebp), %eax     movl    %edx, (%eax)            ;将MyClass的虚函数表地址放在this处     movl    $_ZTV7MyClass+28%edx     movl    8(%ebp), %eax     movl    %edx, 8(%eax)           ;将MyClass虚函数表中属于Base2Class的那部分虚函数放在了this+8处     movl    8(%ebp), %eax     movl    $116(%eax)            ;对数据data1的访问是: this+16     movl    8(%ebp), %eax     movl    $220(%eax)            ;对数据data2的访问是: this+20 
    通过以上的语句和注释, 我们可以发现: 类MyClass的构造函数中, 分别调用了Base1Class和Base2Class的构造函数, 这并不奇怪, 但奇怪的是传递给Base2Class构造函数的this指针变成了MyClass::this+8. 另外, 类MyClass的虚函数表初始时, 分别初始化了两个地方, 一处是this, 一处是this+8. 而类MyClass的两个数据成员data1 和 data2的访问, 也不再是前文单继承情况下的 this+12 和 this+16, 而是多了4个字节. 种种迹象表明, 多继承情况下, 对象结构似乎变成了这样:               |                              |             |------------------------------| this ->     |    Base1Class虚函数表地址    |        0             |------------------------------|             | Base1Class::base_1_data      |        +4             |------------------------------|             |    Base2Class虚函数表地址    |        +8             |------------------------------|             | Base2Class::base_2_data      |        +12             |------------------------------|             |     MyClass::data1           |        +16             |------------------------------|             |     MyClass::data2           |        +20             |------------------------------|             |                              |   下面, 我们再看调用方式的变化. pMyClass->virtual_test_1():  
    movl    -16(%ebp), %eax     ;取this指针     movl    (%eax), %eax        ;取虚函数表地址     movl    (%eax), %edx        ;取virtual_test_1()函数地址     movl    -16(%ebp), %eax     movl    %eax, (%esp)     call    *%edx               ;调用virtual_test_1() 
  pMyClass->virtual_test_2():  
    movl    -16(%ebp), %eax     ;取this指针     movl    8(%eax), %eax       ;this = this + 8     movl    (%eax), %edx        ;取MyClass中属于Base2Class的虚函数表地址,即virtual_test_2()首址     movl    -16(%ebp), %eax     ;取this指针     addl    $8%eax            ;this = this + 8     movl    %eax, (%esp)     call    *%edx               ;调用virtual_test_2() 
  pMyClass->virtual_test_my():  
    movl    -16(%ebp), %eax     ;取this指针     movl    (%eax), %eax        ;取虚函数表地址     addl    $8%eax            ;取virtual_test_my()存放的地址     movl    (%eax), %edx        ;取virtual_test_my()函数首址     movl    -16(%ebp), %eax     movl    %eax, (%esp)     call    *%edx               ;调用virtual_test_my() 
  由此, 可以看出, 在多继承情况下, 在对象结构模型中, 会分开多处存放多个虚函数表的不同起始地址(当然, 虚函数表仍然只有一份, 只是在各处存放的针对于这同一个虚函数表的起始地址不同而已). 那么, 为什么这样作呢?    换个角度想一下, 自然也就明白了. 类MyClass分别继承于两个互不相干的类: Base1Class 和 Base2Class. 由于Base1Class和Base2Class互相没有继承关系, 那么, Base1Class的虚函数表中就不会有Base2Class的虚函数信息, 更重要的, 他们的类成员数据不会被另一个类包含. 反之亦然. 这样, 也就导致同时继承于这二者的类MyClass无法通过唯一的一个this指针来访问分属于两个类的不同的数据, 所以, 把它们分开管理几乎是必然的.   但是, 形如以下的语句:  
    pMyClass = new MyClass;     Base2Class * pBase2Class;     pBase2Class = pMyClass;     pBase2Class->virtual_test_2(); 
  如果pBase2Class的值仍然是pMyClass的this指针, 那么pBase2Class->virtual_test_2()这样的调用, 岂不是有问题了吗? 因为pBase2Class是Base2Class类型, 按Base2Class的类定义, 它的虚函数表是:
    .long    0     .long    _ZTI10Base2Class     .long    _ZN10Base2Class14virtual_test_2Ev     .long    _ZN10Base2Class14virtual_test_3Ev 
  那么, pBase2Class->virtual_test_2()将会被转化成以下形式:  
    movl    -12(%ebp), %eax     ;取this指针     movl    (%eax), %eax        ;取Base2Class虚表地址     movl    (%eax), %edx        ;取virtual_test_2()地址     movl    -12(%ebp), %eax     movl    %eax, (%esp)     call    *%edx 
  但是, 我们知道, pMyClass的虚表明明是:
    .long    0     .long    _ZTI7MyClass     .long    _ZN7MyClass14virtual_test_1Ev     .long    _ZN10Base1Class14virtual_test_3Ev     .long    _ZN7MyClass15virtual_test_myEv     .long    -8     .long    _ZTI7MyClass     .long    _ZN10Base2Class14virtual_test_2Ev     .long    _ZN10Base2Class14virtual_test_3Ev 
  从此虚表中可以看到, virtual_test_2()地址, 应该是+28呀?!   呵呵. 一切玄妙皆在这条赋值语句"pBase2Class = pMyClass;", 这条语句, 偷偷干了这些事:
    movl    -16(%ebp), %eax     ; -16(%ebp) 是 pMyClass       addl    $8%eax            ; eax = pMyClass->this + 8     movl    %eax, -32(%ebp)     jmp    .L31 .L29:     movl    $0-32(%ebp) .L31:     movl    -32(%ebp), %eax     movl    %eax, -12(%ebp)     ;将 this+8 存入了 pBase2Class 变量中 
    this+8! 又是this+8! 没错. 当我们执行 pBase2Class = pMyClass 这条向下兼容的赋值语句时, 编译器会检查他们的继承派生关系, 并将正确的this指针赋给pBase2Class, 而并不是把this指针直接赋值左边的变量. 有人说, C++难, 可能也就是难在这些不容易为人听知的地方吧. 隐藏的东西越多, 学习的开销越大.   在我的测试代码中, 大家可以发现, 我注释掉了一条语句:
    //pMyClass->virtual_test_3(); 
  之所以把它注释掉, 是为了说明这样一个问题: 1. Base1Class和Base2Class可以拥有同名的虚函数, 无引用他们的情况下, 可以编译通过; 2. 但是, 如果有对同名虚函数的引用, 编译器则会报"未决的或容易引起歧义的调用"之类的错误.   但是, 如果换一种方式调用, 则是可以过关的:
    pMyClass = new MyClass;     pBase2Class = pMyClass;     pBase2Class->virtual_test_3(); 
  原因很显然, pBase2Class本身的类型, 已经消除了virtual_test_3()的调用歧义.  

发表于 @ 2006年08月07日 21:44:00|收藏

新一篇: 小品: 关于"C++引用" | 旧一篇: 由一篇BLOG看多态

评论:没有评论。

Csdn Blog version 3.1a
Copyright © 大宝(sodme)