本文主要讲解C++对象模型中的菱形继承的对象模型,分别讨论基类对象变量和函数的继承问题。
何为菱形继承:
菱形继承是指一个基类(Base)派生出两个派生类(Derived1,Derived2),然后这两个派生类(Derived1,Derived2)派生出一个最终的派生类,如1.1的下图所示。
NonVirtualDerivedDiamondClass.cpp
#include
using namespace std;
class Base
{
public:
Base(int x) : x(x) {}
protected:
int x;
};
class Derived1 : public Base
{
public:
Derived1(int y1) : Base(1), y1(y1) {}
protected:
int y1;
};
class Derived2 : public Base
{
public:
Derived2(int y2) : Base(1), y2(y2) {}
protected:
int y2;
};
class DDerived : public Derived1, public Derived2
{
public:
DDerived(int z) : Derived1(11), Derived2(22), z(z) {}
void callX()
{
cout << this->x << endl;
}
protected:
int z;
};
由于最终的派生类包含了基类Base、派生类Derived1,Derived2的对象模型,因此只分析最终派生类DDerived对象模型即可。
VS2017开发者模式查看C++对象模型方法可以参考这篇博客:C++单继承类对象内存布局实战讲解和分析
class DDerived : public Derived1, public Derived2
{
public:
DDerived(int z) : Derived1(11), Derived2(22), z(z) {}
void callX()
{
cout << this->Derived2::x << endl; // 显示指定作用域Derived2::x,调用Derived2的成员变量x
}
protected:
int z;
};
从DDerived内存布局中可以看出,派生类Derived1和Derived2的类对象都各自保存了一份从基类Base继承而来的成员变量int x;这样不但会造成最终派生类DDerived获取变量x出现歧义,同时也会造成内存浪费。那么,是否有办法解决这些问题呢?答案是肯定的,那就是采用虚继承。
由上图可知,只有派生类Derived1和派生类Derived2继承类Base时采用虚继承,而最终派生类DDerived继承Derived1和Derived2时采用普通继承。即
Derived1 : public virtual Base { ... };
Derived2 : public virtual Base { ... };
DDerived : public Derived1, public Derived2 { ... };
VirtualDerivedDiamondClass.cpp
#include
using namespace std;
class Base
{
public:
Base() = default;
Base(int x) : x(x) {}
protected:
int x;
};
class Derived1 : public virtual Base
{
public:
Derived1(int y1) : Base(1), y1(y1) {}
protected:
int y1;
};
class Derived2 : public virtual Base
{
public:
Derived2(int y2) : Base(1), y2(y2) {}
protected:
int y2;
};
class DDerived : public Derived1, public Derived2
{
public:
DDerived(int z) : Derived1(11), Derived2(22), z(z) {}
void callX()
{
cout << this->Derived2::x << endl;
}
protected:
int z;
};
由于最终的派生类包含了基类Base、派生类Derived1,Derived2的对象模型,因此只分析最终派生类DDerived对象模型即可。
VS2017开发者模式查看C++对象模型方法可以参考这篇博客:C++单继承类对象内存布局实战讲解和分析
由上图可知,基类Base和派生类Derived1、Derived2都有虚析构函数和一个虚函数vfun1();,说明这是一个继承中有虚函数的类,即非POD类型的类,内存对象不可逐字节拷贝memcpy(…)。
#include
using namespace std;
class Base
{
public:
Base() = default;
virtual ~Base() {}
Base(int x) : x(x) {}
protected:
int x;
private:
virtual void vfun1() = 0;
};
class Derived1 : public virtual Base
{
public:
Derived1(int y1) : Base(1), y1(y1) {}
virtual ~Derived1() {}
virtual void vfun1() override
{
cout << "virtual Derived1::vfun1()" << endl;
}
protected:
int y1;
};
class Derived2 : public virtual Base
{
public:
Derived2(int y2) : Base(1), y2(y2) {}
virtual ~Derived2() {}
virtual void vfun1() override
{
cout << "virtual Derived2::vfun1()" << endl;
}
protected:
int y2;
};
class DDerived : public Derived1, public Derived2
{
public:
DDerived(int z) : Derived1(11), Derived2(22), z(z) {}
virtual void vfun1() override
{
cout << "virtual DDerived::vfun1()" << endl;
}
void callX()
{
cout << this->x << endl;
}
protected:
int z;
};
图3-1 有虚函数的菱形继承之虚继承图
图3-2 没有虚函数的菱形继承之虚继承图
由上图3-1和对比图3-2可知,有虚函数的菱形继承之虚继承的最终派生类DDerived对象模型跟没有虚函数的菱形继承之虚继承的最终派生类DDerived基本一样,差别只有一个,那就是基类Base多了一个虚指针,该虚指针指向DDerived自身的虚函数表。这个虚函数表跟单继承的虚函数表一样,里面存放的都是DDerived自身的虚函数或者继承而来的虚函数。虚函数表的定义规则是,先将基类虚函数表内容拷贝一份到DDerived自身虚函数表中,然后用DDerived自身的虚函数覆盖虚函数表中同名的虚函数。
同理,当有静态成员函数和静态成员变量、普通成员函数时,DDerived的类内存模型也同样不受影响,具体代码博主就不贴出来了,留一个小作业各位读者自己验证。
c++之菱形继承问题
C++对象模型和布局(三种经典类对象内存布局)
C++中菱形继承的基本概念及内存占用问题
C++之继承(多重继承+多继承+虚继承+虚析构函数+重定义)
《深度探索C++对象模型》 侯捷 page:83-134