• 关于类的虚函数表


        多态是面向对象的特性,通俗说,即用父类指针调用某成员函数时,针对不同对象调用的是不同的函数。从语言层面上看的统一形式的调用(相同名称的虚函数),体现出个性化的行为。 C++ 对象的多态通过虚函数表来实现的,虚函数表属于 C++ 对象模型层面的东西。

        对于一个具体类型来说,它的行为是确定的,举一个简单例子,父类是几何形状,它有一个虚方法是计算自己的面积。对于具体类型例如矩形,正方形,圆形等,他的计算面积的方法是确定的。即对于同一个类型的所有实例,它们的虚函数表的内容相同,在编译时可以确定。

        因此,如果把虚函数表的全部内容附着在对象实例上,这样的对象模型显然是浪费内存的。因此,在对象实例起始处,放的是一个指针,指向其虚函数表,因此每个对象的虚函数表在实例中仅占一个指针(4 bytes)空间。如果一个类型有多个实例,它们指向的是同一份虚函数表(典型情况是位于进程空间的 .rdata section)。虚函数表指针的初始化由编译器生成的各种构造函数负责。如果一个函数不包含虚函数,则对象实例中不包含虚函数表指针。

        虚函数表可以认为是由函数指针组成的数组,数组元素由该类型的所有虚函数的地址组成,用 NULL 表示结尾(取决于编译器对模型的实现)。如果该类型实现了自己的虚函数,它将覆盖从父类继承下来的元素。

        编译器知道表中每个元素对应是那个虚函数,因此调用时取出元素,通过 call 指令实现调用。观察 VC6 debug 版本的汇编代码,虚函数表的内容被编译到只读的 section(和其他常量字符串一起),每个元素代表的是函数的地址(这些元素是代码段起始部的一组 jump 语句的地址,类似中断向量表,用于跳到真正的函数体)。由于虚函数表位于只读 section 中,所以其元素(函数指针)是不能直接改写的。

        下面的代码演示,交换两个实例的虚函数表,从而这两个对象的行为表现将被“交换”。

    test_code
    #include <stdio.h>
    
    class ClassA
    {
    public:
        int a;
    
        ClassA()
        {
            printf("constructor(void).\n");
        }
    
        ClassA(ClassA& t)
        {
            a = t.a;
            printf("copy constructor.\n");
        }
    
        ClassA& operator =(ClassA& t)
        {
            a = t.a;
            printf("asignment operator.\n");
            return *this;
        }
    
        virtual void say()
        {
            printf("ClassA::say();\n");
        }
    
        virtual void say2()
        {
            printf("hello world2.\n");
        }
    };
    
    class ChildClassB : public ClassA
    {
    public:
        virtual void say()
        {
            printf("B::say();\n");
        }
    };
    
    class ChildClassC : public ClassA
    {
    public:
        virtual void say()
        {
            printf("C::say();\n");
        }
    };
    
    
    int main(int argc, char* argv[])
    {
        ClassA *p1 = new ChildClassB();
        ClassA *p2 = new ChildClassC();
    
        p1->say();
        p2->say();
    
        unsigned int *p3 = (unsigned int*)p1;
        unsigned int *p4 = (unsigned int*)p2;
    
        printf("p1's vt: %08X\n", *p3);
        printf("p2's vt: %08X\n", *p4);
    
        //交换两个对象的虚函数表地址
        unsigned int tmp = *p3;
        *p3 = *p4;
        *p4 = tmp;
    
        p1->say();
        p2->say();
    
    
        delete p1;
        delete p2;
        return 0;
    }

        下图是根据 VC6 Debug 版本的代码给出的是 ChildClassB 的对象模型(从《Inside The C++ Object Model》图1.3 修改而来):

        

        从实现机制来看,调用虚函数需要经过运行时的寻址,因此效率不如可以在编译时解析完成的非虚函数。如果不使用多态,即不使用虚函数,仅仅使用 class 进行数据封装和继承的程序风格,则称为“基于对象”(object-based)。

  • 相关阅读:
    简单的几句接口调用,完美完成缩短网站
    nodejs语言实现验证码生成功能
    人工智能:文本相似度分析
    SolrCloud 5.2.1 installation and configuration
    借鉴DP思想: HouseRobberIII
    有意思的数学题:Trapping Rain Water
    API认证方法一览
    FE: Responsive Web Design Overview
    FE: Sass and Bootstrap 3 with Sass
    假期小记:14年寒假二三四事(儿)
  • 原文地址:https://www.cnblogs.com/hoodlum1980/p/2598460.html
Copyright © 2020-2023  润新知