草庐IT

継承的致命缺陷——菱形継承

爱吃鱼的修猫 2024-02-13 原文

目录

前文

一,单继承/多継承

 二,菱形継承

 三,菱形虚拟継承

 3.1 虚拟継承的用法

3.2 解决原理

 四,継承的总结和反思

总结


前文

书接上文,上篇文章我们讲解了一下継承的基础运用,这节我们讲一下継承中惹人诟病的缺点——菱形継承.

一,单继承/多継承

在将菱形継承之前我们需要先讲解一下单継承和多継承

单継承:一个子类只有一个直接父类时称这个继承关系为单继承

 

 多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承,多继承的出现主要是为了应对生活中的一些场景,如圣女果同时具有水果和蔬菜两种属性,但是可能祖师爷也没有想到这会引出来一个大问题——菱形継承。

 

 二,菱形継承

菱形継承其实就是多继承的一个变种,如下图所示

菱形継承的使用场景也是有的,但是它弊大于利,使用起来问题颇多,主要问题有两个。

1.二义性的问题,如上图,B和C都有A的元素,那么D调用A时,调用的时B中的A元素还是B中的A元素呢。

2.数据冗余的问题,如上图,B和C中都有A,这导致継承BC的D中也有两个A,这就造成了数据冗余。

 

class A
{
public:
	int _a;
};

class B :public A
{
protected:
	int _b;
};

class C :public A
{
protected:
	int _c;
};

class D :public B, public C
{
protected:
	int _d;
};
int main()
{
	D d;
	d._a;
	return 0;
}

如上图所示,在访问_a时由于二义性的问题会导致访问不明确,那么我们的祖师爷是怎么解决这个问题呢?

 三,菱形虚拟継承

在C++3.0中,C++引入了虚拟継承来解决菱形継承的二义性和数据冗余问题。

 3.1 虚拟継承的用法

以上面的例子为例,只需要在B和C継承A时在前面加上关键字virtual即可.

class A
{
public:
	int _a;
};

class B :virtual public A
{
protected:
	int _b;
};

class C :virtual public A
{
protected:
	int _c;
};

class D :public B, public C
{
protected:
	int _d;
};
int main()
{
	D d;
	d._a;
	return 0;
}

 成功运行

3.2 解决原理

那么虚拟継承是如何解决菱形継承的二义性和数据冗余的问题的呢?我们可以借助内存窗口观察对象成员的模型

class A
{
public:
	int _a;
};

class B :public A
{
public:
	int _b;
};
//virtual 
class C :public A
{
public:
	int _c;
};

class D :public B, public C
{
public:
	int _d;
};
int main()
{
	D d;
	d.B::_a = 1;
	d.C::_a = 2;
	d._b = 3;
	d._c = 4;
	d._d = 5;


	return 0;
}

此次试验借用上面代码,进行有virtual修饰的継承和无修饰的継承的对象成员内存模型的对比

 上图是没有virtual修饰継承的对象成员的内存模型

 

上图是有virtual修饰的成员变量的内存模型,从上图我们可以得出,D对象将A放到了最下面的空间,这个A同时属于B和C,那么B和C是怎么找到A的呢?这里是通过B和C的两个指针,指向的一张表。这里的指针叫做虚基表指针,这里的表叫做虚基表,虚基表里存着从B或者C到公共区域A的偏移量,然后B和C通过偏移量找到A。

下面是菱形虚拟継承的原理解释

 四,継承的总结和反思

1. C++语法复杂,多继承就是一个很好的体现。本来多继承的出现是为了处理生活中的某些情况,但是由于多继承的出现,就存在菱形继承,有了菱形継承就需要有菱形虚拟継承,这样底层实现就相当复杂,所以一般建议多继承可以用,但是一定不用设计出菱形継承,否则后续的问题会很难解决。

2. 多継承可以认为是C++设计的缺陷,因此后来的很多OO语言(面向对象语言)都没有多继承,如java

3. 継承和组合

1. public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。

2. 组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。

3. 优先使用对象组合,而不是类继承

4. 继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称为白箱复用(white-box reuse)。术语“白箱”是相对可视性而言:在继承方式中,基类的内部细节对子类可见 。继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类间的依赖关系很强,耦合度高。

5. 对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用(black-box reuse),因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被封装。

6.实际尽量多去用组合组合的耦合度低,代码维护性好。不过继承也有用武之地的,有些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用继承,可以用组合,就用组合

总结

上面就是継承的所有内容,希望铁子们可以有所收货

有关継承的致命缺陷——菱形継承的更多相关文章

  1. ruby-on-rails - ActiveRecord 的 find_or_create* 方法是否存在根本性缺陷? - 2

    有几种方法:first_or_create_by、find_or_create_by等,它们的工作原理是:与数据库对话以尝试找到我们想要的东西如果我们找不到,就自己做保存到数据库显然,并发调用这些方法可能会使两个线程都找不到它们想要的东西,并且在第3步中一个线程会意外失败。似乎更好的解决方案是,创建或查找即:提前在您的数据库中创建合理的唯一性约束。如果你想保存一些东西,就保存它如果有效,那就太好了。如果它因为RecordNotUnique异常而无法工作,它已经存在,太好了,加载它那么在什么情况下我想使用Rails内置的东西而不是我自己的(看起来更可靠)create_or_find?

  2. ruby-on-rails - 致命 : Peer authentication failed for user "rails" - 2

    我正在尝试运行rakedb:create在DigitalOcean服务器上使用postgresql。但是,它返回错误Peerauthenticationfailedforuser"rails",引用config/database.yml登录凭据的存储位置奇怪的是,当我通过SSH登录服务器时,这些凭据以纯文本形式显示给我。我都试过了密码以纯文本形式显示给我,同样的事情发生了。环境在生产中,我必须手动强制执行,因为应用程序在启动时正在开发中并强制它在config/environments.rb中更改不工作。如果我不得不猜测,我可能会说环境中发生了一些有趣的事情,因为DigitalOcean

  3. ruby - Mac OSX Lion 和 Ruby - [致命] 分配内存失败 - 2

    我在使用Ruby和OSXLion时遇到严重问题-每当我尝试启动Rails或什至是rake工具时,我都会收到此错误:[FATAL]failedtoallocatememory我尝试在不同版本(1.9.2-p180、1.9.2-p290、1.9.2-head)中重新编译ruby​​,但它总是一样。但是我注意到我可以删除一些gems,然后“rake”工具会起作用,只有“railss”会因该错误而死掉。似乎实际上没有足够的内存来容纳我正在使用的这么多gem,但这又一次看起来很奇怪——它在雪豹上工作得很好,甚至在我公司的macmini上和Lion上工作——唯一的区别是mini升级了SnowLeo

  4. ruby-on-rails - 这个 Rails4 错误是什么意思?致命的 : exception reentered . .. `rescue in rollback_active_record_state!' - 2

    我在我的Rails4约会安排应用程序中遇到了几个错误,我似乎无法更正或找出根本原因。我的种子文件总是因众所周知的“错误,堆栈级别太深”而中断。但是当我运行我认为它正在中断的方法时,我得到了这个不同的错误:Seedingtimeslotsforworkdayno.1(0.5ms)SAVEPOINTactive_record_1(0.5ms)ROLLBACKTOSAVEPOINTactive_record_1fatal:exceptionreenteredfrom/Users/rskelley/.rvm/gems/ruby-2.0.0-p481/gems/activerecord-4.1.

  5. javascript - 在 JavaScript 和 jQuery 中使用委托(delegate)事件处理程序是否存在性能缺陷? - 2

    我在我的JavaScript代码中使用委托(delegate)事件处理程序(jQuery),因此当单击动态添加的按钮时会发生一些事情。我想知道这是否存在性能缺陷?//Delegatedeventhandler$(document).on('click','#dynamicallyAddedButton',function(){console.log("Hello");});在性能方面,它与此相比如何?//Regulareventhandler$("#regularButton").on('click',function(){console.log("HelloAgain");});查看

  6. javascript - 这是对复选框输入的默认点击事件的核心误解,还是有缺陷的代码? - 2

    当绑定(bind)到复选框输入的点击事件时,该复选框在我的事件处理程序运行时已经切换,更奇怪的是,如果我指定event.preventDefault();functionclicked(evt){alert(document.getElementById('foo').checked);evt.preventDefault();}document.getElementById('foo').addEventListener('click',clicked);[在chrome和firefox中测试]JSFiddleforthatcode警报将响应“true”(或复选框预单击的相反状态)。

  7. javascript - Bootstrap 模态有一个缺陷 - 2

    当我学习bootstrap并尝试官方页面上的示例时,我发现了modal的一个缺陷(可能)零件。点击“Launchdemomodal”,你会注意到右上角有一个明显的空白,当模态对话框消失/出现时,导航栏会拉伸(stretch)/收缩。这是错误还是故意的?我觉得这很烦人,如何禁用它? 最佳答案 要手动修复这个问题,只需添加body.modal-open,.modal-open.navbar-fixed-top,.modal-open.navbar-fixed-bottom{margin-right:0px;}在Bootstrap样式表之

  8. javascript - 在没有性能缺陷的情况下打包 JavaScript 代码的最佳方法是什么? - 2

    我正在寻找一种为iPhone压缩JavaScript代码的方法。有没有一种方法可以避免在小型且相当慢的设备上使用大量CPU时间? 最佳答案 使用YUICompressor 关于javascript-在没有性能缺陷的情况下打包JavaScript代码的最佳方法是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/114860/

  9. javascript - 为什么 SoundCloud API 看起来有缺陷? - 2

    我正在构建一个应用程序,并尝试使用“q”过滤器来使用API/tracks。这会查询SoundCloud并根据搜索词检索歌曲。很简单。我的问题:与soundcloud.com本身的搜索功能相比,使用/tracks似乎削弱了搜索功能。通过API获得的结果质量似乎要低得多,而直接在网站上搜索会返回高质量的结果。SoundCloud工程师可以对此发表评论吗? 最佳答案 确保您使用的是“a”参数而不是“查询”。SC.get('/tracks',{q:"keyword"},function(tracks){console.log(tracks)

  10. 2023年企业信息安全缺陷和解决方案,防止职员外泄信息 - 2

    随着网络的发展和普及,信息安全与每个人息息相关,包含方方面。每个人既是独立个体又必须和社会交换资源。这就需要把控一个尺度。要了解信息安全,首先需要对信息有个大体了解。从拥有者和使用者分类分为,个人,企业(个体工商户,集团,公司),国家(军事,银行),公共服务(医院,税务,公园)等。 信息安全从存储介质上分:移动存储(U盘,光盘,磁盘,硬盘,磁带),移动设备(手机,PDA,mini计算机,pad),计算机(个人计算机,企业个人电脑),内部服务器(fileweb,ftp,直播源),云存储(海康云,阿里云,百度云,金山云等),企业自建云,公司混合云,私有云 互联网应用的飞速发展和普及,网络安全越来越

随机推荐