C++之虚拟继承与继承的小总结

本来是想将虚拟继承的部分写在上一篇的,但是虚拟继承的分析实在有些复杂,为了方便我自己回顾,就干脆单写一篇吧。

我们之前说过了,虚拟继承可以解决菱形继承的二义性以及数据冗余的问题,实际上它也就是因为这些问题而诞生的。

虚拟继承中的内存分布

我们就以一个结构最简单的菱形继承为例:

当不使用虚拟继承时:

class A {
public:
	int a;

	A()
		:a(0xa)
	{}
};

class B1 : public A {
public:
	int b1;

	B1()
		:b1(0xb100)
		,A()
	{}
};

class B2 : public A {
public:
	int b2;

	B2()
		:b2(0xb200)
		,A()
	{}
};

class C : public B1, public B2{
public:
	int c1;
	int c2;

	C()
		:c1(0xc10000)
		,c2(0xc20000)
		,B1()
		,B2()
	{}
};

int main() {
	C c;
	while (0);
	return 0;
}

C++之虚拟继承与继承的小总结_第1张图片

可以很直观地看到类A的成员变量a在类C的对象c中存在了两份,并且,很明显这两份又分别存储于对象c中类B1与类B2成员变量所在的空间。

也即:

C++之虚拟继承与继承的小总结_第2张图片

 当使用虚拟继承时:

class A {
public:
	int a;

	A()
		:a(0xa)
	{}
};

class B1 : virtual public A {
public:
	int b1;

	B1()
		:b1(0xb100)
		,A()
	{}
};

class B2 : virtual public A {
public:
	int b2;

	B2()
		:b2(0xb200)
		,A()
	{}
};

class C : public B1, public B2{
public:
	int c1;
	int c2;

	C()
		:c1(0xc10000)
		,c2(0xc20000)
		,B1()
		,B2()
	{}
};

int main() {
	C c;
	while (0);
	return 0;
}

再次查看内存对象成员模型,可以看到,之前重复继承的类A的成员变量被存储到了对象组成的最下面,其原本的位置取而代之的是两个指针——这两个指针各指向一张表,我们将那两张表称为虚基表——所以这两个指针也被称为虚基表指针

虚基表中存储着虚基表指针到对象中虚基类(即A)成员存储位置的偏移量。而编译器也就是根据这个偏移量找到的对象c中A的成员的存储位置。

C++之虚拟继承与继承的小总结_第3张图片

 C++之虚拟继承与继承的小总结_第4张图片

继承的小总结

这个总结本该在上一篇的,但是,没有虚拟继承的继承是不完整的,就当是继承分了个上下篇了。

所谓的C++语法复杂,不好学,多继承就是一个体现。因为有了多继承,从而导致了菱形继承,从而有了数据冗杂以及二义性等问题,为了解决这些问题,有使用了一些复杂的手段在底层实现了虚拟继承......

多继承导致的一系列问题使得其被认为是C++的缺陷之一,C++之后的语言大多都没有多继承。哪怕C++是有多继承的,在设计过程中,也并不建议设计出多继承,并且一定不要设计出菱形继承。这将会导致一系列的问题。

继承与组合(is-a 与 has-a)

is-a 是一种继承关系,我们常说的public继承便是一种is-a关系。就像人与动物一般,每一个人必定是一个动物。每一个派生类对象都是一个基类对象。

has-a 是一种组合关系,是关联关系的一种。你可以说手机有显示屏,但你不能说手机就是显示屏或者显示屏就是手机。has-a 体现的是一种包含,是整体与部分的关系。 

继承允许操作者根据基类的实现来定义派生类的实现。这种方法也会被称为白箱复用。

所谓“白箱”是针对可视性而言的:在继承体系中,基类的内部细节对于子类是可见的。继承在一定程度上破坏了基类的封装,基类实现的改变会对派生类产生很大的影响。基类与派生类之间的耦合度很高。

组合是继承之外的另一种复用手段。一些新的更复杂的功能可以通过组装与组合对象获得。对象组合要求被组合的对象拥有良好定义的接口。这种风格被称为黑箱复用。

与“白箱”相反,“黑箱”的内部细节对外部不可见。被组合的对象内部如何实现,以及其实现的具体细节与组合其的类之间没有太强的依赖关系,耦合度低,可以有效的保持类的封装。

你可能感兴趣的:(笔记,C/C++学习,c++,开发语言)