• 对C++多态的一次小分析


    c++的多态性可以分为两种:

    1.编译时多态:运算符重载和函数重载。这个比较简单,就简单介绍一下,重点是运行时多态。

    运算符重载主要运用c++的operator关键字对运算符重新定义:

    class test
    {
            int a,b;
    public:
            test();
            test(int a,int b);
            void operator>>(test &);
    };
    
    test::test()
    {
            a=b=1;
    }
    
    test::test(int a,int b)
    {
            this->a=a;
            this->b=b;
    }
    
    void test::operator >>(test &p)
    {
            cout<<p.a<<" "<<p.b<<endl;
    }
    
    int main()
    {
            test cout(1,2),b;
            cout>>b;
            return 0;
    }

    函数重载:有一点要记住:只有返回值不同的重定义函数是错误的,其它的不多说了。

    2.运行时多态:其一虚基类表可以说主要来自处理继承的倒平行四边形问题(套用别人的话。。。),即一个基类有多个子类继承,而多个子类又只由一个孙子类继承。如下面一个例子:

    class base
    {
    public:
        int a;
    };
    
    class d1 :public base
    {
    public:
        int b;
    };
    
    class d2 :public base
    {
    public:
        int c;
    };
    
    class d3 :public d1, public d2
    {
    public:
        int sum;
    };
    
    int main()
    {
         d3 _d3;
        _d3.a = 10;
        _d3.b = 20;
        _d3.c = 30;
        _d3.sum = _d3.a + _d3.b + _d3.c;//error
        cout << _d3.sum << endl;
    }        

    注释error的地方就是因为_d3.a不明确,因为即可能是d1,d2的也可能是d3的,那么要解决这个问题,有两个方法:

    第一个是标明作用域即_d3.d1::a,这个方法比较简单并且基本也不用,不再赘述

    第二个就是使用virtual关键字使多类继承只保留一个基类的副本(重点来了!!)

    class base
    {
    public:
        int a;
    };
    
    class d1 :virtual public base
    {
    public:
        int b;
    };
    
    class d2 :virtual public base
    {
    public:
        int c;
    };
    
    class d3 :public base
    {
    public:
        int d;
    };
    class d4 :public d1, public d2
    {
    public:
        int sum;
    };
    
    int main()
    {
        d4 _d4;
        _d4.a = 10;
        _d4.b = 20;
        _d4.c = 30;
        _d4.sum = _d4.a + _d4.b + _d4.c;
        cout << _d4.sum << endl;//correct
    
        cout << sizeof(d1)<< " " << sizeof(d2) << " " << sizeof(d3) << endl;// 输出结果12   12  8
        return 0;
    }

      通过在继承的过程中加上virtual关键字,说明是一个虚基类,虚基类在发生多重继承时,只会保留一个相同的类成员,从而从根本上解决了倒平行四边形效应!

    另外对最后一个输出,可以看出来 没有加virtual关键字的类比加了的少4个字节。这是什么原因呢,通过反汇编可以看一下:

    可以看出每个类都有虚基类表作为对象的数据成员保存,所以上面的程序执行后都会多出4字节来。

    其二虚函数(可以说是运行多态的主要内容了):虚函数 跟函数重载形式上很像,但完全不是一个东西,虚函数必须保证函数名、参数都完全相同,只是函数体不一样而已。

    虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的,即虚表。简称为V-Table。在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其容真实反应实际的函数。这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得由为重要了,它就像一个地图一样,指明了实际所应该调用的函数。

    通过一个例子来分析一下:

    class test
    {
    public:
        virtual void vfunc() { cout << "base's vfunc" << endl; }
    };
    
    class d1 :public test
    {
    public:
        void vfunc() { cout << "d1's vfunc!" << endl; }
    };
    
    class d2 :public test
    {
    public:
        void vfunc() { cout << "d2's vfunc!" << endl; }
    };
    
    int main()
    {
        _asm {
            int 3
        }//用于调试
        test a, *p;
        d1 b;
        d2 c;
        p = &a;
        p->vfunc();
        p = &b;
        p->vfunc();
        p = &c;
        p->vfunc();
        return 0;
    }
    CPU Disasm
    地址                十六进制数据                        指令                                  注释
    0101273E              CC                        int3
    0101273F              8D4D F8                   lea     ecx, [ebp-8]                ; this 指针 a
    01012742              E8 40E9FFFF               call    Demo.01011087               ;  test::test
    01012747              8D4D E0                   lea     ecx, [ebp-20]               ; this 指针 b
    0101274A              E8 9FEBFFFF               call    Demo.010112EE               ;  d1::d1
    0101274F              8D4D D4                   lea     ecx, [ebp-2C]               ; this 指针 c
    01012752              E8 96ECFFFF               call    Demo.010113ED               ;  d2::d2
    01012757              8D45 F8                   lea     eax, [ebp-8]                ; 对象a的地址保存到eax中
    0101275A              8945 EC                   mov     dword ptr [ebp-14], eax     ; 借助对象指针p保存对象a
    0101275D              8B45 EC                   mov     eax, dword ptr [ebp-14]     ; 设置当前this指针为对象a的
    01012760              8B10                      mov     edx, dword ptr [eax]        ; 虚表’vftable
    01012762              8BF4                      mov     esi, esp
    01012764              8B4D EC                   mov     ecx, dword ptr [ebp-14]
    01012767              8B02                      mov     eax, dword ptr [edx]
    01012769              FFD0                      call    eax

    通过反汇编可以看出来,调用虚函数主要是通过this指针区分不同的函数,并且虚表在对象数据的首部。

    下面来看一下vftable虚表数据结构:

    简单来说,继承父类的子类中的虚表算是一个二维数组,盗用一下别人的图,讨论一下多重继承:

    三个父类派生出一个子类,其中子类的f()函数覆盖了父类中的f()函数,来看一下子类中的虚表结构:

    通过这张图我们可以很清晰的看出来每一个父类都在占有一个数组。对于这个程序再进行一次反汇编:

    class Base1
    {
    public:
        virtual void vfunc() { cout << "base's vfunc" << endl; }
    };
    
    class Base2
    {
    public:
        virtual void vfunc() { cout << "d1's vfunc!" << endl; }
    };
    
    class Derive :public Base1,public Base2
    {
    public:
        void vfunc() { cout << "d2's vfunc!" << endl; }
    };
    
    int main()
    {
        _asm {
            int 3
        }
        Derive d;
        d.vfunc();
        return 0;
    }

    通过eax+4可以看出来这个线性结构的递增,即虚表的增加。(这OD突然崩了。崩的莫名其妙。。。本来打算复制过来的,结果只能截屏了,也没办法注释分析了。。。见谅。。)

  • 相关阅读:
    Grunt jshint Warning: Path must be a string . Received null Use
    bootstrap滚动监视原理实现
    Bootstrap模态框原理分析及问题解决
    LeetCode54. 螺旋矩阵
    LeetCode53. 最大子序和
    mysql servers实现层拼写SQL
    easyUI 分页 获取页面
    excel导入功能
    easyUI遮罩
    uuid生成
  • 原文地址:https://www.cnblogs.com/Anony-WhiteLearner/p/8297937.html
Copyright © 2020-2023  润新知