C++类内存分布+钻石模型的解决方法

C++类内存分布

#include<iostream>
using namespace std;
class Base
{
    private:
        int val;
    public:
        Base(int i = 0):val(i){cout<<val<<endl;}
        ~Base(){}
};
class Derived1 : public Base
{
    private:
        int a;
    public:
        Derived1(int i = 1):a(i){cout<<a<<endl;}            
};
class Derived2 : public Base
{
    private:
        int b;
    public:
        Derived2(int i = 2):b(i){cout<<b<<endl;}
};
class Test : public Derived1, public Derived2
{
    private:
        int c;
    public:
        Test(int i = 3):c(i){cout<<i<<endl;}
};

int main()
{
    cout<<sizeof(Base)<<endl;
    cout<<sizeof(Derived1)<<endl;
    cout<<sizeof(Derived2)<<endl;
    cout<<sizeof(Test)<<endl;
    return 0;
}

以下为上述各类在内存中的分布情况:
技术分享
技术分享

我们在基类中加上虚函数 我们再来看看类的内存分布:
技术分享
技术分享
技术分享
由以上我们可以看出,类中多了一个指针成员
其实这个指针成员就是传说中的虚表指针
虚表是一个函数指针数组,它在编译程序时生成
虚表指针是一个函数指针数组的指针,在构造函数中,虚表指针被初始化指向虚表
在非虚继承的派生类中,派生类和基类公用一个虚表指针,这也是实现多态的基础,当我们用一个基类指针指向一个派生类对象,在调用函数时,我们会查阅虚表,进行相应的函数调用,从而实现多态.你可能会问那如果基类指针指向一个基类的对象,那么函数调用岂不是调用到了派生类的函数? 其实不是这样的,当基类指针指向一个基类对象时,它会直接调用基类的函数,而不会去查阅虚表. 需要指出的是查阅虚表需要进行解引用操作,故效率没有直接调用函数高,但是我们为了实现多态,付出这么一点代价也是值得的…

到现在为止,你发现以上的类有什么问题吗?
没错,基类Base的成员在Test类中存在两份,这就可能导致二义性的产生,那么如何解决这个问题呢?
答案是使用虚继承机制!
代码如下:

#include<iostream>
using namespace std;
class Base
{
    private:
        int val;
    public:
        Base(int i = 0):val(i){cout<<val<<endl;}
        virtual ~Base(){}
};
class Derived1 : virtual public Base
{
    private:
        int a;
    public:
        Derived1(int i = 1):a(i){cout<<a<<endl;}            
};
class Derived2 : virtual public Base
{
    private:
        int b;
    public:
        Derived2(int i = 2):b(i){cout<<b<<endl;}
};
class Test : public Derived1, public Derived2
{
    private:
        int c;
    public:
        Test(int i = 3):c(i){cout<<i<<endl;}
};

int main()
{
    cout<<sizeof(Base)<<endl;
    cout<<sizeof(Derived1)<<endl;
    cout<<sizeof(Derived2)<<endl;
    cout<<sizeof(Test)<<endl;
    return 0;
}

以下是上述类内存分布:
技术分享
技术分享
技术分享
我们从以上类的内存分布可以看出,在使用了虚继承机制后,基类成员在派生类中只有一份拷贝,解决了二义性的问题.但是同时,虚继承使得我们和基类不能共用同一个虚表指针,故增加了维护多个虚表的开销!

总结: 当基类中含有虚函数时
1.每个类都将含有虚表指针,虚表在编译期间生成,虚表指针在构造函数中进行初始化
2.当为非虚继承时,派生类和基类共享一个虚表指针
3.当为虚继承时,可以解决钻石模型带来的二义性的问题,但是同时派生类不能和基类共享虚表指针,每个派生类都有两个指针,一个指向基类的虚表,一个指向自己的虚表,这也增加了维护虚表的难度!

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。