Liny_@NotePad

沉迷ACG中

通过虚函数表访问private成员

通过虚函数表可以访问到对象的布局,通过函数指针可以运行函数,不论是private还是public,于是单继承、非virtual的demo如下:

  1. #include <iostream>
  2.  
  3. class A
  4. {
  5. public:
  6.         A(): x(5) { }
  7.  
  8. private:
  9.         virtual void fun() { std::cout << "A::fun()" << std::endl; }
  10.  
  11. private:
  12.         int x;
  13. };
  14.  
  15. class B: public A
  16. {
  17. public:
  18.         B(): y(3) { }
  19.  
  20. private:
  21.         virtual void fun() { std::cout << "B::fun()" << std::endl; }
  22.  
  23. private:
  24.         int y;
  25. };
  26.  
  27. typedef void (*Fun)();
  28.  
  29. void PrintVTable(Fun* pVT)
  30. {
  31.         Fun* pFun = pVT;
  32.         while( *pFun )
  33.         {
  34.                 (*pFun)();
  35.                 pFun ++;
  36.         }
  37. }
  38.  
  39. void PrintMembers(int* pMembers)
  40. {
  41.         std::cout << *pMembers << std::endl;
  42. }
  43.  
  44. void PrintVTableAndMembers(B* ptr)
  45. {
  46.         int* pAddress = (int*) ptr;
  47.         PrintVTable((Fun*) *pAddress);
  48.         pAddress ++;
  49.  
  50.         PrintMembers(pAddress);
  51.         pAddress ++;
  52.  
  53.         PrintMembers(pAddress);
  54.         pAddress ++;
  55. }
  56.  
  57. void main(void)
  58. {
  59.         B b;
  60.  
  61.         PrintVTableAndMembers(&b);
  62.  
  63.         system("pause");
  64. }

如果是多继承 或者 继承方式用virtual,对象的布局是不一样的,可以参考下本文:http://yoyo.is-programmer.com/posts/10671.html

多重继承的虚函数表与类型转换

总结:

  • 非virtual继承:
    进行隐式转换或进行显式转换均可时(代码中简写为隐式强转),最左父类指针指向子类时指向地址不变,其他父类指针指向子类时指向地址需要偏移,根据指针的类型对虚函数进行地址偏移(访问指针类型对象的虚函数表)。
    必须进行显式转换时指针指向地址均不变,按照强制转换前的类型对虚函数进行地址偏移(访问原来类型的虚函数表)。
    但是它们的实际函数都是访问子类的实际虚函数。
     
  • virtual继承:
    进行隐式转换或进行显式转换均可时(代码中简写为隐式强转),指针指向子类时指向地址均需要偏移,根据指针的类型对虚函数进行地址偏移(访问指针类型对象的虚函数表)。
    必须进行显式转换时指针指向地址均不变,按照强制转换前的类型对虚函数进行地址偏移(访问原来类型的虚函数表)。
    但是它们的实际函数都是访问子类的实际虚函数。

virtual继承与非virtual继承的差别其实仅在于不需要显式转换时的最左父类指针是否偏移。这与当时的虚函数表布局方式有关。
virtual继承将新的virtual方法都写在自身vftable中; 而非virtual继承则将新的virtual方法更新到最左父类的vftable中。

【20090821】C++培训日记-虚函数表

检测方法(VS2005):项目命令行加上参数/d1reportAllClassLayout,在编译时CTRL+F5搜索输出,查看类的对象布局。

vftable - 虚函数表; vbtable - 虚继承的父类表; member - 类的成员变量(这个只是写作方便说明 = =)。

总结:

  • 继承方式:非virtual继承:导入各个父类的结构(按照父类声明的顺序,从上到下),自身member在最后
    • 重写virtual方法:更新该方法最早定义的类的vftable
    • 新的virtual方法:在最左父类的vftable增加
  • 继承方式:有virtual继承:在自身member后增加virtual父类的结构(按照子类继承的顺序从左到右),同时在最前面增加vbtable(如果没有的话),增加一项指向父类结构
    • 重写virtual方法:更新该方法的最早定义的类的vftable
    • 新的virtual方法:在自身最前面增加vftable(如果没有的话),在自己的vftable增加

【20090820】C++培训日记-进阶1

今天学习的内容:this指针、继承(不使用虚函数)、多态、单继承与虚函数表