对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突然崩了。崩的莫名其妙。。。本来打算复制过来的,结果只能截屏了,也没办法注释分析了。。。见谅。。)

原文地址:https://www.cnblogs.com/Anony-WhiteLearner/p/8297937.html