C++虚继承小结

来源:互联网 发布:六年级新教材辅导软件 编辑:程序博客网 时间:2024/05/05 00:19

虚继承对类的对象布局的影响

要理解多重继承情况中重复基类时为什么会出现访问路径不明确的编译错误,需要了解继承中类对象在内存中的布局。在C++继承中,子类会继承父类的成员变量,因此在子类对象在内存中会包括来自父类的成员变量。实例代码如下,输出结果表明了每个对象在内存中所占的大小。

#include<iostream>
using std::cout;
using std::endl;
class Base
{
protected:
int value;
public:
Base()
{
//cout<<"in Base"<<endl;
}
};
class DerivedA:protected Base
{
protected:
int valueA;
public:
DerivedA()
{
//cout<<"in DerivedA"<<endl;
}
};
class DerivedB: protected Base
{
protected:
int valueB;
public:
DerivedB()
{
//cout<<"in DerivedB"<<endl;
}
};
class MyClass:DerivedA
{
private:
int my_value;
public:
MyClass()
{
//cout<<"in MyClass"<<value<<endl;
}
};
int main()
{
Base base_obj;
DerivedA derA_obj;
MyClass my_obj;
cout<<"size of Base object "<<sizeof(base_obj)<<endl;
cout<<"size of DerivedA object "<<sizeof(derA_obj)<<endl;
cout<<"size of MyClass object "<<sizeof(my_obj)<<endl;
}

输出结果如下

 

从类的定义结合这里的输出便不难明白,在子类对象中是包含了父类数据的,即在C++继承中,一个子类的object所表现出来的东西,是其自己的members加上其基类的member的总和。示意图如下(这里只讨论非静态变量)

 

在单继承的时候,访问相关的数据成员时,只需要使用名字即可。但是,在多重继承时,情况会变得复杂。因为重复基类中,在子类中变量名是相同的。这时,如果直接使用名字去访问,便会出现歧义性。看下面的代码以及对应的输出

#include<iostream>
using std::cout;
using std::endl;
class Base
{
protected:
int value;
public:
Base()
{
//cout<<"in Base"<<endl;
}
};
class DerivedA:protected Base
{
protected:
int valueA;
public:
DerivedA()
{
//cout<<"in DerivedA"<<endl;
}
};
class DerivedB: protected Base
{
protected:
int valueB;
public:
DerivedB()
{
//cout<<"in DerivedB"<<endl;
}
};
class MyClass:DerivedA,DerivedB
{
private:
int my_value;
public:
MyClass()
{
//cout<<"in MyClass"<<value<<endl;
}
};
int main()
{
Base base_obj;
DerivedA derA_obj;
MyClass my_obj;
cout<<"size of Base object "<<sizeof(base_obj)<<endl;
cout<<"size of DerivedA object "<<sizeof(derA_obj)<<endl;
cout<<"size of MyClass object "<<sizeof(my_obj)<<endl;
}

输出如下

 

代码的变化之处在于MyClass同时继承了DerivedA和DerivedB。而my_obj在内存中的大小变成了20,比之前大了8.正好是增加了继承至DerivedB中的数据部分的大小。上面情况中,my_obj在内存中的布局示意图如下

 

 

从图中可以看到,来自Base基类的数据成员value重复出现了两次。这也正是为什么在MyClass中直接访问value时会出现访问不明确的问题了。

那么使用虚继承后,对象的数据在内存中的布局又是什么样子呢?按照预测,既然在my_obj中只有一份来自Base的value,那么大小是否就是16呢?

代码及输出如下

#include<iostream>
using std::cout;
using std::endl;
class Base
{
protected:
int value;
public:
Base()
{
//cout<<"in Base"<<endl;
}
};
class DerivedA:protected virtual Base
{
protected:
int valueA;
public:
DerivedA()
{
//cout<<"in DerivedA"<<endl;
}
};
class DerivedB: protected virtual Base
{
protected:
int valueB;
public:
DerivedB()
{
//cout<<"in DerivedB"<<endl;
}
};
class MyClass:DerivedA,DerivedB
{
private:
int my_value;
public:
MyClass()
{
//cout<<"in MyClass"<<value<<endl;
}
};
int main()
{
Base base_obj;
DerivedA derA_obj;
DerivedB derB_obj;
MyClass my_obj;
cout<<"size of Base object "<<sizeof(base_obj)<<endl;
cout<<"size of DerivedA object "<<sizeof(derA_obj)<<endl;
cout<<"size of DerivedB object "<<sizeof(derB_obj)<<endl;
cout<<"size of MyClass object "<<sizeof(my_obj)<<endl;
};

输出结果如下

 

可以看到,DerivedA和DerivedB对象的大小变成了12,而MyClass对象的大小则变成了24.似乎大大超出了我们的预料。这其实是由于编译器在其中插入了一些东西用来寻找这个共享的基类数据所用而造成的。(来自《深度探索C++对象模型》第3章 侯捷译)这样理解,Class如果内含一个或多个虚基类子对象,那么将被分割为两部分:一个不变部分和一个共享部分。不变局部中的数据,不管后继如何衍化,总是拥有固定的offset,所以这一部分数据可以直接存取。至于共享局部,所表现的就是虚基类子对象。根据编译其的不同,会有不同的方式去得到这部分的数据,但总体来说都是需要有一个指向这部分共享数据的指针。

示意图如下

 

当然实际编译器使用的技术比这个要复杂,这里就不做详细讨论了。感兴趣的朋友可以参见《深入探索C++对象模型》

本篇文章来源于 Linux公社网站(www.linuxidc.com)  原文链接:http://www.linuxidc.com/Linux/2012-11/74492p3.htm

原创粉丝点击