最近学习C++,知道在菱形继承关系中虚继承的出现可以解决派生类数据冗余问题,那么虚拟继承后的派生类是什么样子呢?
1.首先说一下什么是菱形继承的二义性问题
引用Siddhartha Rao在<21天学通c++>第八版11.2节的话:
"在继承层次结构中,继承多个从同一个类派生而来的基类时,如果这些基类没有采用虚
继承,将导致二义性。这种二义性被称为菱形问题(Diamond Problem)"
"C++关键字 virtual 的含义随上下文而异(我想这样做的目的很可能是为了省事),对其含
义总结如下:
在函数声明中, virtual 意味着当基类指针指向派生对象时,通过它可调用派生类的相应
函数。
从 Base 类派生出 Derived1 和 Derived2 类时,如果使用了关键字 virtual,则意味着再从
Derived1 和 Derived2 派生出 Derived3 时,每个 Derived3 实例只包含一个 Base 实例。
也就是说,关键字 virtual 被用于实现两个不同的概念"
介绍一篇讲得比较好的虚拟继承原理的文章, 其中虚基类表的内容我在linux ubuntu上用gdb命令没有看出什么结果来,日后若有知晓回头补充
【转】https://www.2cto.com/kf/201604/498612.html
【转】https://blog.csdn.net/melody_1016/article/details/52966355
2.自己写代码验证上述结论并看清楚虚继承下对象的内存结构
基类:Base,两个子类KidOne,KidTwo虚继承于Base,并派生出Tuna
根据打印结果我们可以看到实际上Tuna对象实质上只有一个Base对象,而KidOne,KidTwo中多了虚基类表指针vbptr(当然这是编译器行为, 不同编译器可能对虚继承有不同的处理, linux Ubuntu的原理目前没有搞清楚,暂且先这样理解)
(1)构造顺序
Base,KidOne,KidTwo,Tuna
需要注意的是&myTuna的地址是0xbfab3b44,而Base对象的地址是0xbfab3b58
所以在内存中实际的排布是(从低地址->高地址)
vbptr(KidOne)->kidone->vbptr(KidTwo)->kidtwo->tuna->base
(2)结构分析
因为在32位系统下,所以指针大小为4,在公司64位系统试过指针大小为8
可以看出myTuna对象中实际只有一个Base对象,实际上这个Base被KidOne与KidTwo共享了
root@ubuntu:/lianxi/lianxi_c++/duotai# ./b.out
call Base constructor, this = 0xbfab3b58
call KidOne constructor, this = 0xbfab3b44
call KidTwo constructor, this = 0xbfab3b4c
call Tuna constructor, this = 0xbfab3b44
&myTuna = 0xbfab3b44
=====SIZEOF TEST=====
sizeof(Base) = 4
sizeof(KidOne) = 12
sizeof(KidTwo) = 12
sizeof(Tuna) = 24
=====STRUCTURE TEST=====
Please verify, p = 0xbfab3b44
*p = 0x80491b4
*(p + 1) = 1
*(p + 2) = 0x80491c0
*(p + 3) = 2
*(p + 4) = 3
*(p + 5) = 0
call Tuna destroyer, this = 0xbfab3b44
call KidTwo destroyer, this = 0xbfab3b4c
call KidOne destroyer, this = 0xbfab3b44
call Base destroyer, this = 0xbfab3b58
root@ubuntu:/lianxi/lianxi_c++/duotai#
/*Name :virtual_inherit_sample.cpp
*Purpose:describe the structure of virtual inherit object
*Date :2018-12-15
*/
#include <iostream>
#include <stdio.h>
using namespace std;
class Base
{
private:
int base;
public:
/*constructor*/
Base(int baseInit = 0) : base(baseInit)
{
cout << "call Base constructor, this = " << this << endl;
}
/*destroyer*/
~Base(void)
{
cout << "call Base destroyer, this = " << this << endl;
}
};/*class Base*/
class KidOne : virtual public Base
{
private:
int kidone;
public:
/*constructor*/
KidOne(int kidoneInit = 1) : kidone(kidoneInit)
{
cout << "call KidOne constructor, this = " << this << endl;
}
/*destroyer*/
~KidOne(void)
{
cout << "call KidOne destroyer, this = " << this << endl;
}
};/*class KidOne*/
class KidTwo : virtual public Base
{
private:
int kidtwo;
public:
/*constructor*/
KidTwo(int kidtwoInit = 2) : kidtwo(kidtwoInit)
{
cout << "call KidTwo constructor, this = " << this << endl;
}
/*destroyer*/
~KidTwo(void)
{
cout << "call KidTwo destroyer, this = " << this << endl;
}
};/*class KidTwo*/
class Tuna : public KidOne, public KidTwo
{
private:
int tuna;
public:
/*constructor*/
Tuna(int tunaInit = 3) : tuna(tunaInit)
{
cout << "call Tuna constructor, this = " << this << endl;
}
/*destroyer*/
~Tuna(void)
{
cout << "call Tuna destroyer, this = " << this << endl;
}
};/*class Tuna*/
/*test on 32bit OS*/
int main(void)
{
Tuna myTuna;
int* p = NULL;
cout << "&myTuna = " << &myTuna << endl;
putchar(10);
printf("=====SIZEOF TEST=====\n");
printf("sizeof(Base) = %d\n", sizeof(Base)); /*4*/
printf("sizeof(KidOne) = %d\n", sizeof(KidOne));/*12*/
printf("sizeof(KidTwo) = %d\n", sizeof(KidTwo));/*12*/
printf("sizeof(Tuna) = %d\n", sizeof(Tuna)); /*24*/
putchar(10);
printf("=====STRUCTURE TEST=====\n");
p = (int*)(&myTuna);
printf("Please verify, p = %p\n", p);
printf("*p = %p\n", *p);
printf("*(p + 1) = %d\n", *(p + 1));/*1*/
printf("*(p + 2) = %p\n", *(p + 2));
printf("*(p + 3) = %d\n", *(p + 3));/*2*/
printf("*(p + 4) = %d\n", *(p + 4));/*3*/
printf("*(p + 5) = %d\n", *(p + 5));/*0*/
return (0);
}