CSDN首页 空间 新闻 论坛 Blog 下载 读书 网摘 搜索 .NET Java 视频 接项目 求职 在线学习 买书 程序员 通知
IBM Rational 系统开发最佳实践工具包 WebSphere MQ 最佳实践 TOP 15
CSDN社区
搜索 收藏 打印 关闭
CSDN社区 >  C/C++ >  C++ 语言

问个问题,怎么实现虚函数机制?

楼主sssa2000()2006-03-03 21:56:23 在 C/C++ / C++ 语言 提问

虽然知道vtable,但是像自己实现发现还是有巨大的困难  
  哪位能帮帮我,网上找到一篇代码   可是运行后代码访问异常,  
  大家帮我看看吧:  
  #include   <stdio.h>  
      class   Base   {  
      public:  
              void     Output()   {  
                      printf("Class   Base\n");  
            }  
      };  
      class   Derive   :   public   Base   {  
      public:  
              void     Output()   {  
                      printf("Class   Derive\n");  
              }  
      };  
   
      void   Test(Base   *p)   {  
            p->Output();  
    }  
   
  int   __cdecl   main(int   argc,   char*   argv[])    
  {  
      Derive   obj;                                                                                
      typedef   void   (__stdcall   *PFUNC)(void*);                            
      void   *pThis   =   &obj;                                                              
      PFUNC   pFunc   =   (PFUNC)*(unsigned   int*)pThis;                
      pFunc   =   (PFUNC)*(unsigned   int*)pFunc;                            
      return   0;  
   
  }  
  问题点数:20、回复次数:16Top

1 楼cunsh(村少)回复于 2006-03-03 22:11:05 得分 0

class   Base   {  
      public:  
              void     Output()   {  
                      printf("Class   Base\n");  
            }  
      virtual   ~Base(){}           //   加个虚函数.对象里才含有vptr  
      };Top

2 楼xiaocai0001(高楼目尽欲黄昏/梧桐叶上萧萧雨)回复于 2006-03-03 22:14:30 得分 0

虚函数需要virtual   关键字申明.  
   
  有virtual后,   编译器就会对这样的函数建立一个vtable  
  动态调用时,   按照这个vtable去搜索相应的函数去执行,   这是运行期的动态  
  Top

3 楼sssa2000()回复于 2006-03-03 22:23:46 得分 0

.......  
  看来我没说清楚  
  我的意思是,不使用关键字virtual来实现动态邦定。  
  所以程序里面用了很多函数指针。Top

4 楼guyanhun(老婆说的都是对的!努力做个好老公!)回复于 2006-03-03 23:12:40 得分 0

编译器会自动为你加一个   vptr   ,  
  然后函数指针加一个偏移地址(就是说当前虚函数所在vtable   中的下标   *   4)运行期得到真正的函数地址。Top

5 楼zenny_chen(ACE Intercessor)回复于 2006-03-03 23:13:51 得分 0

动态绑定是由编译器事前准备的,用户程序无法模拟。  
  在目标代码中已经有比较清晰的跳转调用。Top

6 楼Jinhao(辣子鸡丁·GAME就这样OVER了)回复于 2006-03-03 23:26:53 得分 10

1,   http://blog.csdn.net/jinhao/archive/2004/01/17/4797.aspx    
  2,   http://blog.csdn.net/jinhao/archive/2004/01/17/4799.aspx  
  3,   http://blog.csdn.net/jinhao/archive/2004/01/17/4798.aspx  
  Top

7 楼lei001(太极)回复于 2006-03-03 23:27:50 得分 0

深入浅出mfc这本书上,有关于虚拟函数的机制介绍的Top

8 楼zzw820626(偶要分,偶要星星)回复于 2006-03-04 13:25:53 得分 0

编译器就会对这样的函数建立一个vtable  
  Top

9 楼ox_thedarkness()回复于 2006-03-04 14:46:37 得分 10

先来一个简单的例子,为了类型安全用到了模版和成员函数指针。  
  和虚函数表机制不同之处在于:为了清晰,我没使用单独的虚函数表,而是对每个函数保留一个指针。  
   
  class   Base{  
      Base(); //   禁用默认构造函数  
      void   (Base::*_msg)(); //   两个函数指针。第一个输出类的信息  
      void   (Base::*_seti)(int); //   第二个设置_i的属性  
   
  protected:  
      int   _i;  
   
      //   构造函数为保护,强制要求设置函数指针  
      template<   class   T   >  
      Base(   void   (T::*   msg)(),   void(   T::*   seti)(int)   )  
      :_msg(   (void(Base::*)())   msg   ),_seti(   (void(Base::*)(int))   seti   ),_i(0){}  
  public:  
      //   公开接口,通过虚函数指针调用虚函数  
      void   msg(){   (this->*_msg)();   }  
      void   seti(   int   i   ){   (this->*_seti)(   i   );   }  
  };  
   
   
  //=========================================  
  //基类写法很复杂,但是派生类就很容易写了:  
  class   A:   public   Base{  
  public:  
      A():   Base(   &A::msg,   &A::seti   ){};  
      void   msg(){   cout<<"A,   #"<<   _i   <<endl;   }  
      void   seti(   int   i   ){   _i   =   2*   i;   }  
  };  
   
   
  int   main(){  
      A   a;  
      a.seti(   10   );  
      a.msg();  
  }Top

10 楼ox_thedarkness()回复于 2006-03-04 16:42:51 得分 0

上面的代码问题在于:  
   
  1   每个虚函数需要一个单独的指针,假如虚函数过多,类的体积就太大。同时也有冗余。  
   
  2   虚析构函数问题。   当对基类指针调用   delete,   必须调用派生类的析构函数。  
   
  3   只能用于单继承,所有基类偏移都是0。   假如多继承,则基类的this和派生类this不相同。  
  假如用于多继承,每个函数都必须有修正为正确的偏移。  
   
  4   多继承时释放问题。此时传递给   operator   delete   的指针可能是错误的,必须修正偏移。  
   
   
  楼主有兴趣可以研究下上面四个问题的手动模拟。前两个比较好办,后两个则过于麻烦。下面解释下我的思路:  
   
   
  解决方案分析如下:  
  问题1    
  基类使用一个虚函数表结构指针VPTR。   每个派生类提供一个静态虚函数表VTABLE,构造函数中设置VPTR为自己的虚函数表VTABLE。  
   
  问题2  
  设置一个自定义虚函数完成析构工作。在基类的析构函数中调用这个虚函数   ——   注意:派生类的析构函数则什么都不要做!!   很简单,不同于C++真正的虚析构函数,我们只是模拟调用。   派生类析构时会自动调用基类的析构函数。  
   
   
   
  前两个问题不难实现。但是后面的问题,涉及到RTTI。需要对派生类大量手动簿记编码工作,实在是容易出错而且划不来。既然C++提供了虚函数和RTTI,那么我们没必要手动完成他们。   不过这里还是提出手动解决方案:  
   
   
  问题3  
  在自定义虚函数表中提供类型信息,表示每个虚函数的实际偏移。这个挺难封装的。  
   
   
  问题4  
  重载基类的   operator   delete,并且配对重载   operator   new(安全性考虑)。读取自定义虚函数表中对象描述部分,正确计算偏移并且删除。    
   
  很有趣的是,C++   中这对咚咚居然能够自动继承...       顺便说一句,在C++本身中也是一样,必须打开RTTI才能正确删除多继承时基类指针。Top

11 楼ox_thedarkness()回复于 2006-03-04 17:14:25 得分 0

口胡,   下面是虚函数表的实现。  
  解决了上面的问题1   、   2,但是没解决多继承问题。     呵哈...   蛮好玩的。  
   
   
  class   Base{  
  protected:  
      template<   class   T   >  
      struct   VTABLE{  
          void   (T::*_msg)(); //   两个函数指针。第一个输出类的信息  
          void   (T::*_seti)(int); //   第二个设置_i的属性  
          void   (T::*_delBase)(); //   析构函数  
      };  
   
  private:  
      Base(); //   禁用默认构造函数  
      const   VTABLE<Base>   *_vbptr; //_vbptr,名称"_vptr"在DevC++下与隐藏成员冲突..  
   
  protected:  
      int   _i;  
   
      //   构造函数为保护,强制要求设置函数指针  
      template<   class   T   >  
      Base(   VTABLE<T>*   vp   ):   _vbptr(   (   VTABLE<Base>*   )vp   ),_i(0){}  
  public:  
      //   公开接口,通过虚函数指针调用虚函数  
      void   msg(){    
          if(   _vbptr   &&   _vbptr->_msg)   (this->*_vbptr->_msg)();  
      }  
      void   seti(   int   i   ){  
          if(   _vbptr   &&   _vbptr->_seti)   (this->*_vbptr->_seti)(i);  
      }  
      ~Base(){  
          if(   _vbptr   &&   _vbptr->_delBase)   (this->*_vbptr->_delBase)();  
      }  
  };  
   
   
   
  class   A:public   Base{  
      static   VTABLE<A>   vtable;  
      void   msg(){   cout<<"A,   #"<<   _i   <<endl;   }  
      void   seti(   int   i   ){   _i   =   2*   i;   }  
      void   delBase(){   cout<<"~A()"<<endl;   }  
  public:  
      A():   Base(   &vtable   ){};  
  };  
  A::VTABLE<A>   A::vtable   =   {   &A::msg,   &A::seti,   &A::delBase   };  
   
   
   
  //========================================  
  //   测试用函数  
  void   testBase(   Base&   b   ){  
      b.seti(   10   );  
      b.msg();  
  }  
  void   delBase   (   Base*   pb   ){  
      delete   pb;  
  }  
   
  int   main(){  
      //   测试1:     派生类和自动析构  
      {  
          A   a;  
          testBase(   a   );  
      }  
      cout<<"--------------------------"<<endl;  
   
      //   测试2:     基类指针   delete  
      {  
          A*   pa   =   new   A();  
          delBase(   pa   );  
      }  
  }  
  Top

12 楼ugg(逸学堂(exuetang.net))回复于 2006-03-04 21:48:58 得分 0

more   effective   C++有一节讲解编程实现虚函数机制。  
  Top

13 楼sssa2000()回复于 2006-03-09 23:21:20 得分 0

楼上各位的解答都十分精彩阿,给100分都不够啊Top

14 楼ox_thedarkness()回复于 2006-03-09 23:45:23 得分 0

//很有趣的是,C++   中这对咚咚居然能够自动继承...       顺便说一句,在C++本身中也是一样,必须打开RTTI才能正确删除多继承时基类指针。  
   
  楼主顶上来了阿。   那就更正一下,C++   RTTI   和   删除多继承时基类指针无关。   多继承时,非最左基类的析构函数必须是虚函数数,才能正确删除基类指针,避免错位问题。Top

15 楼pankun(剑神一笑 Console下面干革命)回复于 2006-03-10 00:07:42 得分 0

所以我建议程序员一定要掌握汇编呢.了解了汇编这些都不是问题.自己看一下反汇编中,怎么调用虚函数的就明白了.  
   
  一般就这样的形式  
  mov                   eax,类指针    
  mov                   edx,[eax]                   ;   取得VMT表地址  
  mov                   ecx,类地址                 ;   隐式的传递类指针给类成员函数  
  call                 dword   ptr   [edx+4]   ;   调用VMT表中的第二个虚函数Top

16 楼sssa2000()回复于 2006-03-11 22:49:14 得分 0

汇编看过,  
  gcc输出的和vc输出的都看过了  
  就是想自己实现一下,看到以上几位的回帖  
  已经搞定了,谢谢大家Top

相关问题

  • 大家说说数据窗口Retrieve函数的实现机制!
  • 如何想实现回调函数的机制
  • 怎样实现虚构造函数?
  • 为什么说虚机制在构造函数中不工作?
  • 针对构造函数中虚机制的再一次提问?
  • 类定义中定义的函数可以不实现吗(非纯虚函数)????
  • 为什么用静态函数实现父类里的虚函数会出错?
  • 虚函数?
  • 纯虚函数!
  • 虚函数

关键词

  • .net
  • 函数
  • 指针
  • 编译器
  • 代码
  • virtual
  • csdn
  • blog
  • vbptr
  • seti

得分解答快速导航

  • 帖主:sssa2000
  • Jinhao
  • ox_thedarkness

相关链接

  • C/C++ Blog
  • C/C++类图书
  • C/C++类源码下载

广告也精彩

反馈

请通过下述方式给我们反馈
反馈
提问
网站简介|广告服务|VIP资费标准|银行汇款帐号|网站地图|帮助|联系方式|诚聘英才|English|问题报告
北京创新乐知广告有限公司 版权所有, 京 ICP 证 070598 号
世纪乐知(北京)网络技术有限公司 提供技术支持
Copyright © 2000-2008, CSDN.NET, All Rights Reserved
GongshangLogo