第53课 被遗弃的多重继承(上)

1. 单一继承

(1)实验代码

#include <iostream>
#include <string>

using namespace std;

void visitVtbl(int **vtbl)
{
    cout << vtbl << endl;
    cout << "\t[-1]: " << (long)vtbl[-1] << endl;

    typedef void (*FuncPtr)();
    int i=0;
    while(vtbl[i]!=0)
    {
        cout << "\t[" << i << "]: " << vtbl[i] << " -> ";
        FuncPtr func = (FuncPtr)vtbl[i];
        func();
        ++i;
    }
}

class Base
{
    public:
        Base()
        {
            mBase1 = 101;
            mBase2 = 102;
        }
        virtual void func1()
        {
            cout << "Base::func1()" << endl;
        }
        virtual void func2()
        {
            cout << "Base::func2()" << endl;
        }
    private:
        int mBase1;
        int mBase2;
};

class Derived : public Base
{
    public:
        Derived():
            Base()
        {
            mDerived1 = 1001;
            mDerived2 = 1002;
        }
        virtual void func2()
        {
            cout << "Derived::func2()" << endl;
        }
        virtual void func3()
        {
            cout << "Derived::func3()" << endl;
        }
    private:
        int mDerived1;
        int mDerived2;
};

int main()
{
    Derived d;
    char *p = (char*)&d;
    visitVtbl((int**)*(int**)p);
    p += sizeof(int**);

    cout << *(int*)p << endl;
    p += sizeof(int);

    cout << *(int*)p << endl;
    p += sizeof(int);

    cout << *(int*)p << endl;
    p += sizeof(int);

    cout << *(int*)p << endl;

    return 0;
}

/*输出结果
0x404338
        [-1]: 4211452
        [0]: 0x402998 -> Base::func1()
        [1]: 0x402a20 -> Derived::func2()
        [2]: 0x402a50 -> Derived::func3()
101
102
1001
1002
*/

(2)对象的内存模型

(2)针对单一继承可以得出以下结论

  ①vptr位于对象的最前端。

  ②非static的成员变量根据其继承顺序和声明顺序排在vptr的后面。

  ③派生类继承基类所声明的虚函数,即基类的虚函数地址会被复制到派生类的虚函数表中的相应的项中。

  ④派生类中新加入的virtual函数跟在其继承而来的virtual的后面,如本例中,子类增加的virtual函数func3被添加到func2后面。

  ⑤若子类重写其父类的virtual函数,则子类的虚函数表中该virtual函数对应的项会更新为新函数的地址,如本例中,子类重写了virtual函数func2,则虚函数表中func2的项更新为子类重写的函数func2的地址。

2. 多重继承

(1)实验代码

#include <iostream>
#include <string>

using namespace std;

void visitVtbl(int **vtbl, int count)
{
    cout << vtbl << endl;
    cout << "\t[-1]: " << (long)vtbl[-1] << endl;

    typedef void (*FuncPtr)();
    for (int i = 0; vtbl[i] && i < count; ++i)
    {
        cout << "\t[" << i << "]: " << vtbl[i] << " -> ";
        FuncPtr func = (FuncPtr)vtbl[i];
        func();
    }
}

class Base1
{
    public:
        Base1()
        {
            mBase1 = 101;
        }
        virtual void funcA()
        {
            cout << "Base1::funcA()" << endl;
        }
        virtual void funcB()
        {
            cout << "Base1::funcB()" << endl;
        }
    private:
        int mBase1;
};

class Base2
{
    public:
        Base2()
        {
            mBase2 = 102;
        }
        virtual void funcA()
        {
            cout << "Base2::funcA()" << endl;
        }
        virtual void funcC()
        {
            cout << "Base2::funcC()" << endl;
        }
    private:
        int mBase2;
};

class Derived : public Base1, public Base2
{
    public:
        Derived():
            Base1(),
            Base2()
        {
            mDerived = 1001;
        }
        virtual void funcD()
        {
            cout << "Derived::funcD()" << endl;
        }
        virtual void funcA()
        {
            cout << "Derived::funcA()" << endl;
        }
    private:
        int mDerived;
};

int main()
{
    Derived d;
    char *p = (char*)&d;
    visitVtbl((int**)*(int**)p, 3);
    p += sizeof(int**);

    cout << *(int*)p << endl;
    p += sizeof(int);

    visitVtbl((int**)*(int**)p, 3);
    p += sizeof(int**);

    cout << *(int*)p << endl;
    p += sizeof(int);

    cout << *(int*)p << endl;

    return 0;
}

/*输出结果
0x4043a8
        [-1]: 4211520
        [0]: 0x402a88 -> Derived::funcA()
        [1]: 0x4029b8 -> Base1::funcB()
        [2]: 0x402ab8 -> Derived::funcD()
101
0x4043bc
        [-1]: 4211520
        [0]: 0x402b28 -> Derived::funcA()
        [1]: 0x402a38 -> Base2::funcC()
102
1001
*/

(2)对象的内存模型

(3)钍对多重继承可以得出以下结论

  ①在多重继承下,一个子类拥有n-1张额外的虚函数表,n表示其上一层的基类的个数。也就是说,在多重继承下,一个派生类会有n个虚函数表。其中一个为主要实例,它与第一个基类(如本例中的Base1)共享,其他的为次要实例,与其他基类(如本例中的Base2)有关。

  ②子类新声明的virtual函数,放在主要实例的虚函数表中。如本例中,子类新声明的与Base1共享的虚函数表中。

  ③每一个父类的子对象在子类的对象保持原样性,并依次按声明次序排列。

  ④若子类重写virtual函数,则其所有父类中的签名相同的virtual函数被会被改写。如本例中,子类重写了funcA函数,则两个虚函数表中的funcA函数的项均被更新为子类重写的函数的地址。这样做的目的是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。

3. 重复继承:某个父类被间接地重复继承了多次

(1)实验代码

#include <iostream>
#include <string>

using namespace std;

void visitVtbl(int **vtbl, int count)
{
    cout << vtbl << endl;
    cout << "\t[-1]: " << (long)vtbl[-1] << endl;

    typedef void (*FuncPtr)();
    for (int i = 0; vtbl[i] && i < count; ++i)
    {
        cout << "\t[" << i << "]: " << vtbl[i] << " -> ";
        FuncPtr func = (FuncPtr)vtbl[i];
        func();
    }
}

class Base
{
    public:
        Base()
        {
            mBase = 11;
        }
        virtual void funcA()
        {
            cout << "Base::funcA()" << endl;
        }
        virtual void funcX()
        {
            cout << "Base::funcX()" << endl;
        }
    protected:
        int mBase;
};
class Base1 : public Base
{
    public:
        Base1():
            Base()
        {
            mBase1 = 101;
        }
        virtual void funcA()
        {
            cout << "Base1::funcA()" << endl;
        }
        virtual void funcB()
        {
            cout << "Base1::funcB()" << endl;
        }
    private:
        int mBase1;
};
class Base2 : public Base
{
    public:
        Base2():
            Base()
        {
            mBase2 = 102;
        }
        virtual void funcA()
        {
            cout << "Base2::funcA()" << endl;
        }
        virtual void funcC()
        {
            cout << "Base2::funcC()" << endl;
        }
    private:
        int mBase2;
};
class Derived : public Base1, public Base2
{
    public:
        Derived():
            Base1(),
            Base2()
        {
            mDerived = 1001;
        }
        virtual void funcD()
        {
            cout << "Derived::funcD()" << endl;
        }
        virtual void funcA()
        {
            cout << "Derived::funcA()" << endl;
        }
    private:
        int mDerived;
};

int main()
{
    Derived d;
    char *p = (char*)&d;
    visitVtbl((int**)*(int**)p, 4);
    p += sizeof(int**);

    cout << *(int*)p << endl;
    p += sizeof(int);

    cout << *(int*)p << endl;
    p += sizeof(int);

    visitVtbl((int**)*(int**)p, 3);
    p += sizeof(int**);

    cout << *(int*)p << endl;
    p += sizeof(int);

    cout << *(int*)p << endl;
    p += sizeof(int);

    cout << *(int*)p << endl;

    return 0;
}

/*输出结果
0x404408
        [-1]: 4211552
        [0]: 0x402b68 -> Derived::funcA()
        [1]: 0x402a08 -> Base::funcX()
        [2]: 0x402a88 -> Base1::funcB()
        [3]: 0x402b98 -> Derived::funcD()
11
101
0x404420
        [-1]: 4211552
        [0]: 0x402c08 -> Derived::funcA()
        [1]: 0x402a08 -> Base::funcX()
        [2]: 0x402b10 -> Base2::funcC()
11
102
1001
*/

(2)对象的内存模型

(3)钍对重复继承可以得出以下结论

  ①重复继承后,位于继承层次顶端的父类Base分别被子类Base1和Base2继承,并被类Derived继承。所以在D中有类的对象中,存在Base1的子对象,同时也存在Base2的子对象,这两个子对象都拥有Base子对象,所以Base子对象(成员mBase)在Derived中存在两份。

  ②二义性的原因。由于在子类的对象中,存在两份父类的成员,当在Derived类中使用如下语句:

  mBase = 1;

  就会产生歧义。因为在该对象中有两处的变量的名字都叫mBase,所以编译器不能判断究竟该使用哪一个成员变量。所以在访问Base中的成员时,需要加上域作用符来明确说明是哪一个子类的成员,如:

  Base1::mBase = 1;

  重复继承可能并不是我们想要的,C++提供虚拟继承来解决这个问题,下面详细讲解虚拟继承.

4. 单一虚拟继承:

(1)实验代码:具体代码如下(类的实现与重复继承中的代码相同,只是Base1的继承关系变为虚拟继承)

(2)对象的内存模型

(3)钍对单一虚拟继承可以得出以下结论

  ①成员的顺序问题。在普通的单一继承中,基类的成员位于派生类的成员之前。而在单一虚继承中,首先是其普通基类的成员,接着是派生类的成员,最后是虚基类的成员。

  ②vptr的个数问题。在普通的单一继承中,派生类只有一个虚函数表,所以其对象只有一个vptr。而在单一虚继承中,派生类的虚函数表有n个(n为虚基类的个数)额外的虚数函数表,即总有n+1个虚函数表。

  ③派生自虚基类的派生类的虚函数表中,并不含有虚基类中的virtual函数,但是派生类重写的virtual函数会在所有虚函数表中得到更新。如本例中,第一个虚函数表中,并不含有Base::funcX的函数地址。

  ④一个类如果内含一个或多个虚基类子对象,像Base1那样,将会被分割为两部分:一个不变区域和一个共享区域。不变区域中的数据,不管后续如何变化,总是拥有固定的偏移量(从对象的开头算起),所以这一部分可以被直接存取。共享区域所对应的就是虚基类子对象。

5. 钻石型虚拟继承

(1)实验代码:具体代码如下(类的实现与重复继承中的代码相同,只是Base1和Base2的继承关系变为虚拟继承):

class Base  { ...... };

class Base1 : virtual public Base  { ...... };

class Base2 : virtual public Base  { ...... };

class Derived : public Base1, public Base2 { ...... };

(2)对象的内存模型

(3)钍对钻石型虚拟继承可以得出以下结论

  ①使用虚继承后,在派生类的对象中只存在一份的Base子对象,从而避免了二义性。由于是多重继承,且有一个虚基类(Base),所以Derived类拥有三个虚函数表,其对象存在三个vptr。如上图所示,第一个虚函数表是由于多重继承而与第一基类(Base1)共享的主要实例,第二个虚函数表是与其他基类(Base2)有关的次要实例,第三个是虚基类的虚函数表。

  ②类Derived的成员与Base1中的成员排列顺序相同,首先是以声明顺序排列其普通基类的成员,接着是派生类的成员,最后是虚基类的成员。

  ③派生自虚基类的派生类的虚函数表中,也不含有虚基类中的virtual函数,派生类重写的virtual函数会在所有虚函数表中得到更新。

  ④在类Derived的对象中,Base(虚基类)子对象部分为共享区域,而其他部分为不变区域。

时间: 2024-10-12 17:25:46

第53课 被遗弃的多重继承(上)的相关文章

第53课 被遗弃的多重继承

问题:C++中是否允许一个类继承自多个父类呢?答案是肯定的,这种现象就是多重继承多重继承是C++中一个特有的特性,因为在其他的程序设计语言里面,如C#.java等语言只支持单重继承 C++支持编写多重继承的代码-一个子类可以拥有多个父类-子类拥有所有父类的成员变量-子类继承所有父类的成员函数-子类对象可以当作任意父类对象使用 多重继承的语法规则 class Derived: public BaseA, public BaseB, public BaseC { // .... }; 多重继承的本质

第53课 被遗弃的多重继承 (下)——正确的使用多重继承

工程开发中的多重继承方式:(这是面向对象理论中所推荐的方式)单继承某个类 + 实现(多个)接口 #include <iostream> #include <string> using namespace std; class Base { protected: int mi; public: Base(int i) { mi = i; } int getI() { return mi; } bool equal(Base* obj) { return (this == obj);

第53课 被遗弃的多重继承 (中)

多重继承的问题三:多重继承可能产生多个虚函数表 #include <iostream> using namespace std; class BaseA { public: virtual void funcA() { cout << "BaseA::funcA()" << endl; } }; class BaseB { public: virtual void funcB() { cout << "BaseB::funcB(

第54课 被遗弃的多重继承(下)

1. C++中的多重继承 (1)一个子类可以拥有多个父类 (2)子类拥有所有父类的成员变量 (3)子类继承所有父类的成员函数 (4)子类对象可以当作任意父类对象使用 (5)多重继承的语法规则 class Derived: public BaseA, public BaseB, public BaseC{…}; 2. 多重继承问题一 (1)通过多重继承得到的对象可以拥有“不同的地址”!!! (2)解释方案:无 (3)原因分析 [编程实验]多重继承问题一 #include <iostream> u

C++--被遗弃的多重继承、经典问题

一.被遗弃的多重继承 Q:C++中是否允许一个类继承自多个父类?在实际的C++编译环境中,C++是支持编写多重继承的代码1.一个子类可以拥有多个父类2.子类拥有所有父类的成员变量3.子类继承所有父类的成员函数4.子类对象可以当作任意父类对象使用多重继承的语法规则但是在多重继承中会存在许多问题Q:多重继承得到的对象可能拥有不同的地址代码示例 #include <iostream> #include <string> using namespace std; class BaseA {

第五十三课、被遗弃的多重继承(上)

一.c++的多重继承 1.c++支持编写多重继承的代码 (1).一个子类可以拥有多个父类 (2).子类拥有所有父类的成员变量 (3).子类继承所有父类的成员函数 (4).子类对象可以当做任意父类对象使用 2.多重继承产生的问题一:通过多重继承得到的对象拥有不同的地址 #include<iostream> using namespace std; class BaseA { protected: int ma; public: BaseA(int a) { ma = a; } int GetA(

第十八课 特权级转移(上)

这一节我们来研究从核心代码特权级转移到应用代码特权级. 首先将boot.asm贴出来如下: 1 org 0x7c00 2 3 jmp short start 4 nop 5 6 define: 7 BaseOfStack equ 0x7c00 8 BaseOfLoader equ 0x9000 9 RootEntryOffset equ 19 10 RootEntryLength equ 14 11 EntryItemLength equ 32 12 FatEntryOffset equ 1 1

第53课:Hive 第一课:Hive的价值、Hive的架构设计简介

一. Hive的历史价值 1, 大数据因Hadoop而知名,而Hadoop又因Hive而实用.Hive是Hadoop上的Killer Application,Hive是Hadoop上的数据仓库,同时Hive兼具有数据仓库中的存储和查询引擎.而Spark SQL是一个更加出色和高级的查询引擎,并不提供存储功能.所以Spark SQL无法取代Hive,在现在企业级应用中Spark SQL+Hive成为了业界使用的大数据最为高效和流行的趋势. 2,Hive是Facebook推出的,主要是为了让不懂ja

第58课 自定义模型类(上)

1. QStandardItemModel是一个通用的模型类 (1)能够以任意的方式组织数据(线性.非线性) (2)数据组织的基本单位为数据项(QStandardItem) (3)每一个数据项能够存储多个具体数据(可通过附加数据角色加以区别) (4)每一个数据项能够对数据状态进行控制(可编辑.可选.--) 2. Qt中的通用模型类:QStandartItemModel (1)QStandardItemModel继承自QAbstractItemModel. (2)QStandardItemMode