zoukankan      html  css  js  c++  java
  • 虚函数表(转)

    考点:“DLL HELL”的了解
    出现频率:★★
    答案:
    “DLL HELL”主要是指DLL(动态链接库)版本冲突的问题。一般情况下DLL新版本会覆盖旧版本,那么原来使用旧版本的dll的应用程序就会不能继续正常工作了。
     
    扩展知识:虚函数表
    大家知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。在这个表中,主要是一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,其内容真实反应实际的函数。这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以,当用父类的指针来操作一个子类的时候,这张虚函数表就显得尤为重要了,它就像一个地图一样,指明了实际所应该调用的函数。
    在C++的标准规格说明书中说到,编译器必须保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证正确取到虚函数的偏移量)。这意味着通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。请看下面程序例子:
    1       #include <iostream>
    2       using namespace std;
    3      
    4       class Base
    5       {
    6       public:
    7                virtual void fun1() {cout << "Base::fun1" << endl;}
    8                virtual void fun2() {cout << "Base::fun2" << endl;}
    9                virtual void fun3() {cout << "Base::fun3" << endl;}
    10     private:
    11              int num1;
    12              int num2;
    13     };
    14    
    15     typedef void (*Fun)(void);
    16    
    17     int main()
    18     {
    19              Base b;
    20              Fun pFun;
    21             
    22              pFun = (Fun)*((int*)*(int*)(&b)+0);      //取得Base::fun1()地址
    23              pFun();                             //执行Base::fun1()
    24              pFun = (Fun)*((int*)*(int*)(&b)+1);      //取得Base::fun1()地址
    25              pFun();                             //执行Base::fun2()
    26              pFun = (Fun)*((int*)*(int*)(&b)+2);      //取得Base::fun1()地址
    27              pFun();                             //执行Base::fun3()
    28    
    29              return 0;
    30     }
    上面程序的执行结果如下:
    1       Base::fun1
    2       Base::fun2
    3       Base::fun3
    可以看到通过函数指针pFun的调用,分别执行了对象b的三个虚函数。通过这个示例发现,可以通过强行把&b转成int *,取得虚函数表的地址,然后再次取址就可以得到第一个虚函数的地址了,也就是Base::fun1()。如果要调用Base::fun2()和Base::fun3(),只需要把&b先加上数组元素的偏移,后面的步骤类似就可以了。
    程序中的Base对象b内存结构图,如图9.3所示。

    &bp

    vfptr
    num1
    num2
     
     

    Base::fun1()      Base::fun2()     Base::fun3()     NULL

    图9.3  Base虚函数表图
    一个类会有多少张虚函数表呢?
    对于一个单继承的类,如果它有虚拟函数,则只有一张虚函数表。对于多重继承的类,它可能有多张虚函数表。
    考虑下面代码中的各个类的定义:
    1       #include <iostream>
    2       using namespace std;
    3      
    4       class Base1
    5       {
    6       public:
    7                Base1(int num) : num_1(num) {}
    8                virtual void foo1() {cout << "Base1::foo1 " << num_1 << endl;}
    9                virtual void foo2() {cout << "Base1::foo2 " << num_1 << endl;}
    10              virtual void foo3() {cout << "Base1::foo3 " << num_1 << endl;}
    11     private:
    12              int num_1;
    13     };
    14    
    15     class Base2
    16     {
    17     public:
    18              Base2(int num) : num_2(num) {}
    19              virtual void foo1() {cout << "Base2::foo1 " <<num_2 << endl;}
    20              virtual void foo2() {cout << "Base2::foo2 " <<num_2 << endl;}
    21              virtual void foo3() {cout << "Base2::foo3 " <<num_2 << endl;}
    22     private:
    23              int num_2;
    24     };
    25    
    26     class Base3
    27     {
    28     public:
    29              Base3(int num) : num_3(num) {}
    30              virtual void foo1() {cout << "Base3::foo1 " << num_3 << endl;}
    31              virtual void foo2() {cout << "Base3::foo2 " << num_3 << endl;}
    32              virtual void foo3() {cout << "Base3::foo3 " << num_3 << endl;}
    33     private:
    34              int num_3;
    35     };
    36    
    37     class Derived1 : public Base1
    38     {
    39     public:
    40              Derived1(int num) : Base1(num) {}
    41              virtual void faa1() {cout << "Derived1::faa1" << endl;}    //无覆盖
    42              virtual void faa2() {cout << "Derived1::faa2" << endl;}
    43     };
    44    
    45     class Derived2 : public Base1
    46     {
    47     public:
    48              Derived2(int num) : Base1(num) {}
    49              virtual void foo2() {cout << "Derived2::foo2" << endl;}    //只覆盖了Base1::foo2
    50              virtual void fbb2() {cout << "Derived2::fbb2" << endl;}
    51              virtual void fbb3() {cout << "Derived2::fbb3" << endl;}
    52     };
    53    
    54     class Derived3 : public Base1, public Base2, public Base3   //多重继承,无覆盖
    55     {
    56     public:
    57              Derived3(int num_1, int num_2, int num_3) :
    58                       Base1(num_1), Base2(num_2), Base3(num_3) {}
    59              virtual void fcc1() {cout << "Derived3::fcc1" << endl;}
    60              virtual void fcc2() {cout << "Derived3::fcc2" << endl;}
    61     };
    62    
    63     class Derived4 : public Base1, public Base2, public Base3    //多重继承,有覆盖
    64     {
    65     public:
    66              Derived4(int num_1, int num_2, int num_3) :
    67                       Base1(num_1), Base2(num_2), Base3(num_3) {}
    68              virtual void foo1() {cout << "Derived4::foo1" << endl;}    //覆盖了Base1::foo1,
    69                                                          //Base2::foo1, Base3::foo1
    70              virtual void fdd() {cout << "Derived4::frr" << endl;}
    71     };
    这个例子说明了四种继承情况下的虚函数表。
    (1)一般继承(无虚函数覆盖):Derived1类继承自Base1类,Derived1的虚函数表如图9.4所示。

    vfptr

    Base1::foo1()

    Base1::foo2()

    Base1::foo3()

    NULL

    图9.4  Derived1虚函数表图
    Derived1类内没有任何覆盖基类Base1的函数,因此两个虚拟函数faa1()和faa2()被依次添加到了虚函数表的末尾。
    (2)一般继承(有虚函数覆盖):Derived2类继承自Base1类,并对Base1类中的虚函数foo2()进行了覆盖。Derived2的虚函数表如图9.5所示。

    vfptr

    Base1::faa3()

    Derived2::fbb2()

    Derived2::fbb3()

    NULL

    Derived2::foo2()

    Base1::foo1()

    图9.5  Derived2虚函数表图
    Derived2覆盖了基类Base1的faa1(),因此其虚函数表中Derived2::foo2()替换了Base1::foo2()一项,fbb2()和fbb3()被依次添加到了虚函数表的末尾。
    (3)多重继承(无虚函数覆盖):Derived3类继承自Base1类、Base2类、Base3类,Derived3的虚函数表如图9.6所示。

    Vfptr
    (Base1)

    Base1::foo1()

    Base1::foo2()

    Base1::foo3()

    Derived3::fcc1()

    Derived3::fcc2()
     

    NULL

    Vfptr
    (Base2)

    Vfptr
    (Base3)

    Base2::foo1()

    Base2::foo2()

    Base2::foo3()

    NULL

    Base3::foo3()

    NULL

    Base3::foo1()

    Base3::foo2()

    图9.6  Derived3虚函数表图
    每个父类都有自己的虚表,Derived3也就有了三个虚表,并且成员函数fcc1()和fcc2()被放到了第一个父类(Base1)的表中,这里所谓的第一个父类是按照声明顺序来判断的。这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。例如:
    1       Base2 *pBase2 = new Derived3();
    2       pBase2->foo2();   //调用Base2::foo2()
    把Base2类型的指针指向Derive3实例,那么调用将是对应Base2虚表里的那些函数。
    (4)多重继承(有虚函数覆盖):Derived4类继承自Base1类、Base2类、Base3类,并对Base1类的foo1()、Base2类的foo1()、Base3类的foo1()都进行了覆盖。Derived4的虚函数表如图9.7所示。
     

    Vfptr
    (Base1)

    Derived4::foo1()

    Base1::foo2()

    Base1::foo3()

    Derived4::fdd()

    NULL

    Vfptr
    (Base2)

    Vfptr
    (Base3)

    Derived4::foo1()

    Base2::foo2()

    Base2::foo3()

    NULL

    Base3::foo3()

    NULL

    Derived4::foo1()

    Base3::foo2()

    图9.7  Derived4虚函数表图
    可以看见,Base1::foo1()、Base2::foo1()和Base3::foo1()都被替换成了Derived::foo1()。这样,我们就可以把任一个静态类型的父类来指向子类,并调用子类的f()了。如:
    1       Base1*pBase1 = new Derived4();
    2       pBase1->foo1();             //调用从Base1继承的虚表中的Derived4::foo1()
    下面是我们所讨论的四种继承情况下的测试代码:
    1       int main()
    2       {
    3                Base1 *pBase1 = NULL;
    4                Base2 *pBase2 = NULL;
    5                Base3 *pBase3 = NULL;
    6      
    7                cout << "-----  一般继承自Base1,无覆盖  ----------" << endl;
    8                Derived1 d1(1);        //Derived1一般继承自Base1,无覆盖
    9                pBase1 = &d1;   
    10              pBase1->foo1(); //执行Base1::foo1(); 
    11    
    12              cout << "-----  一般继承自Base1,覆盖foo2()  ---------" << endl;
    13              Derived2 d2(2);        //Derived2一般继承自Base1,覆盖了Base1::foo2()
    14              pBase1 = &d2;
    15              pBase1->foo2(); //执行Derived2::foo2();
    16    
    17              cout << "-----  多重继承,无覆盖  ----------" << endl;
    18              Derived3 d3(1, 2, 3);    //Derived3多重继承自Base1,Base2,Base3,没有覆盖
    19              pBase1 = &d3;
    20              pBase2 = &d3;
    21              pBase3 = &d3;
    22              pBase1->foo1();  //执行Base1::foo1();
    23              pBase2->foo1();  //执行Base2::foo1();
    24              pBase3->foo1();  //执行Base3::foo1();
    25             
    26              cout << "------  多重继承,覆盖foo1()  ---------" << endl;
    27              Derived4 d4(1, 2, 3);    //Derived4多重继承自Base1,Base2,Base3,覆盖foo1()
    28              pBase1 = &d4;
    29              pBase2 = &d4;
    30              pBase3 = &d4;
    31              pBase1->foo1();        //执行Derived4::foo1();
    32              pBase2->foo1();        //执行Derived4::foo1();
    33              pBase3->foo1();        //执行Derived4::foo1();
    34              return 0;
    35     }
    测试结果如下:
    1           一般继承自Base1,无覆盖  ----------
    2       Base1::foo1 1
    3       一般继承自Base1,覆盖foo2()
    4       Derived2::foo2
    5       -----  多重继承,无覆盖  ----------
    6       Base1::foo1 1
    7       Base2::foo1 2
    8       Base3::foo3 3
    9       ------  多重继承,覆盖foo1()  ---------
    10     Derived4::foo1
    11     Derived4::foo1
    12     Derived4::foo1

  • 相关阅读:
    BOM 事件 navigator浏览器的判断
    闭包
    超简单超实用的走马灯效果实现
    对DOM的增删改查
    SVN版本回退与常用命令总结
    mongodb使用总结
    如何理解 IE 的文档兼容模式
    软件开发编码规范
    第一篇绑定数据 CMS
    关于IIS7.5下的web.config配置的一些问题
  • 原文地址:https://www.cnblogs.com/feng801/p/1493231.html
Copyright © 2011-2022 走看看