基类吧,为什么老看见说说虚基类表更像接口

C++ 虚基类表指针字节对齐模型
关于虚基类表指针/虚函数表指针这些类里面的“隐藏成员”在结构里面是如何进行字节对齐的这个问题困惑了我48个小时。虽然网上也有很多关于虚继承、虚函数表、虚基类等内存布局的一些文章,但是基本上谈的都是大致的布局,什么地方有什么,按照什么顺序来排放等这些泛泛而谈的东西,好像都没有把字节对齐方面考虑进去的文章,或许他们都没有考虑到一些复杂层次的类继承关系,从而字节对齐的问题也没有成为问题,也自然而然地会被忽略掉,或者得出错误的对齐规则结论。
也许是我一开始就走错了路,在错误中越走越远,尝尽了各种曲扭的解决方案来回波折。我原以为我对字节对齐的规则已经很熟悉了,而恰恰是这仅仅几个字节的东西却困惑了我两三天,所以我觉得有必要将这个东西单独写一篇笔记,以记录我的研究成果和各种憋屈的时光。我们的目标是,精确到每一个字节~!
在Windows 里面微软是使用了虚基类表的方式来实现类的虚继承的,简单来讲就是当有虚继承的时候会在类里面加入一个“隐藏成员”虚基类表指针,这个指针就是指向虚基类表的了。我们要讨论的就是这个隐藏成员的字节级别的布局,其他的不在这里讨论。从现在开始我会一步一步地让事情变得更加复杂。
先看下面这片代码。在这里我使用了一个空类K,不要被这个东西所迷惑,我使用这个空类的目的主要是为了让它产生虚基类表指针而又不引入虚基类成员变量,这样我就可以少叙述一些关于虚基类成员排放的东西,而将焦点聚集在引入的那个虚基类表指针之上。这个空类虽然有点特殊,但是在这里它其他的东西和正常的类一样,不要纠结这个。还有,代码我直接指定了对齐参数,是为了不引起混乱。
#include"stdafx.h"
#pragma pack(8)
class K{};
class B :virtual K{
B() :b(0xbbbbbbbb){}
int _tmain(int argc, _TCHAR* argv[])
我们可以在IDE下打断点,用内存查看窗口轻易地观察到对象bb的内存布局情况,这个大家基本上都应该猜测得到大致的布局,就是在类B的开始插入了一个隐藏成员,虚基类表指针,然后才到B的成员b,因为虚基类没有成员所以其他的就不需要理了,这就是我选空类的原因,少说点话~。在VS2013下我抓到的布局是这样的:
恩,和我们猜测的一样。先是放了虚基类表指针然后才到 B 的成员。看到这里我们视乎可以得出点小结论,就是加入的隐藏成员,虚基类表指针,就是相当于在类里面加入一个正常的指针成员变量一样,即可以把类B看成是这样的等价布局:
class B { void *vb_}
这样的话字节对齐等各方面都还算是合理的。
那么我们小改一下看下能推翻这个模型么。我们在B里面加一个成员变量double。变成这样:
#include "stdafx.h"
#pragma pack(8)
class K{};
class B :virtual K{
double b2;
B() :b(0xbbbbbbbb), b2(0){}
int _tmain(int argc, _TCHAR* argv[])
按照以上的理论,我们新加入的double类型的成员变量b2刚好会紧紧的排列在成员b的后面,因为字节对齐刚好合适。但是不幸的是抓到的实际内存布局是这样的:
你看,原来在vb_ ptr和成员b之间没有填充字节的,现在却有了4个字节的填充,而b2和b之间的4字节填充倒还可以勉强理解得过去。所以之前的推论肯定是不正确的了。按照现在的布局和之前的布局貌似还可以这样牵强地理解,虚基类表指针不和B是一个结构的,B是一个结构,虚基类表指针自己一个结构,那么就等价于这样的布局:
class temp{ void* vb_
B tempB;};
为什么是上面这种布局,不懂的需要看另一篇博客:
简单的解释如下:
在 temp 类里面
vb_ptr 是一个指针,大小为 4 字节;
tempB 是一个结构体,tempB 中又包含 int 类型(占 4 字节)的变量 b ;
以及double 类型(占 8 字节)的变量 b2;
class 或 struct 类型的有效对齐参数是指它的成员中,有效对齐参数最大的那个值,对于 temp 类而言,有效对齐参数是 8 ,也就是 tempB 中的 b2 变量(double类型)占用的字节大小。
按照上述模型的话就可以满足上面的两种情况了,而且好像怎么变化B里面的成员,这个模型都可以很好的等价其对齐规则。这个结论当然也是错误的,一般情况其实也就到这里了,但是我说过要让事情变得复杂。
#include"stdafx.h"
#pragma pack(8)
class K{};
A() :a(0xaaaaaaaa){}
class B :virtual K, A{
B() :b(0xbbbbbbbb){}
int _tmain(int argc, _TCHAR* argv[])
我们把代码改成如上那样。让B在虚继承的时候还多继承了一个A,而且是实继承A。这个派生类B的对象模型估计很多人也猜得对,按照对象布局规则,先是实基类A,然后是虚基类表指针,然后是成员b。实际上内存布局确实也是这样:
class temp{ A tempA; void* vb_
B tempB;};
但是后面,我们会慢慢的进入噩梦。小改一下代码。把类A的成员变量a的类型改成char类型。3个类现在变成这样,指定对齐仍然是8字节对齐,和VC默认的一样。
#include "stdafx.h"
#pragma pack(8)
class K{};
// int-&char
A() :a(0xaaaaaaaa){}
class B :virtual K, A{
B() :b(0xbbbbbbbb){}
int _tmain(int argc, _TCHAR* argv[])
在IDE下抓取到的B的对象bb的内存布局是这样的:
怎么样,虚函数表指针后面居然填充了4个字节,看起来貌似完全没有必要在和B的成员变量b之间填充4个字节,就算是b直接排放在vb_prt之后也是满足整体的对齐规则的。这样看来,虚基类表指针的对齐规则貌似和排在它之前的对象又有点联系。
我们把指定的对齐参数改成4字节对齐。看下这填充的4个字节会消失掉么。理论上它应该消失,因为填充的字节数必须小于指定的对齐参数的。
#include"stdafx.h"
#pragma pack(4) //pack(8)-&pack(4)
class K{};
//int-&char
A() :a(0xaaaaaaaa){}
class B :virtualK, A{
B() :b(0xbbbbbbbb){}
int _tmain(int argc, _TCHAR* argv[])
可以看到,字节的填充完全没有变化,这确实是违背了对齐规则的,这种情况不应该发生~!。这也是苦恼我很久的地方。然而,还有更加苦恼的地方。
把对齐改回8字节对齐,然后再把B的成员b的类型改成double。
#include"stdafx.h"
#pragma pack(8)
class K{};
//int-&char
A() :a(0xaaaaaaaa){}
class B :virtualK, A{
//int-&double
B() :b(0xbbbbbbbb){}
int _tmain(int argc, _TCHAR* argv[])
把b的类型由int改成了double之后,虚基类表指针和B的成员b之间居然填充了8个字节。实在是匪夷所思了。我尝试了很多猜测,很多看似马上就对得上号了的模型,但是最终都被自己举各种复杂的例子给推翻了~···当然,我想到的那些对齐模型都是很曲扭的,其实连我自己也不相信会是那样的~···
后来,不知道怎么的灵感就来了。
像虚基类表指针和虚函数表指针这些类里面必要的时候会出现的“隐藏成员变量”它们的对齐规则可以总结为一句话:
隐藏成员的加入不能影响在其后的成员的对齐。
这句话怎么理解呢?要想不影响后面的成员的对齐规则和填充的字节,那么因为隐藏成员而插入的总体字节长度就必须是结构里面的各个成员中有效对齐参数最大的那个的整数倍。这样,后面的成员才可以“无视”隐藏成员的存在进行对齐和填充。隐藏的成员确确实实也属于类的成员。
这个结论我已经测试了很多种情况了,都是可以的,我也相信像这种言简意赅的结论才会是正确的那一个,其实我早该想到,而且微软的编译器这样安排也确实很合情合理。我之前的各种憋屈结论立马变成浮云……
我们先按照这个规则小试牛刀一下。
比如最匪夷所思的这个:
#include"stdafx.h"
#pragma pack(4) //pack(8)-&pack(4)
class K{};
//int-&char
A() :a(0xaaaaaaaa){}
class B :virtualK, A{
B() :b(0xbbbbbbbb){}
int _tmain(int argc, _TCHAR* argv[])
类A的大小只占一个字节,这已经是它的完整的结构,a和虚基类表指针之间的那3个字节是因为虚基类表指针本身需要4字节对齐而引入的填充字节,在虚基类表指针之后,因为填充的3个字节和vb_ptr本身的4个字节加起来才是7个字节,还不是结构B中的有效对齐参数最大的那个的整数倍,B中成员的最大对齐参数是int b 或者是隐藏成员虚基类表指针本身的4字节对齐,这其实也是类B本身的有效对齐参数。所以还得在vb_ptr后面再补一个字节,这样后面的成员才能相当于无视掉这个隐藏参数带来的对齐影响。而后面那3个框出来的CC,其实是成员b需要4字节对齐而引入的字节填充,所以看起来就像是填充了4个字节而已。其实它并没有违反字节对齐的基本规则。
还有其他几个也是可以一样的推导出来。
虚函数表指针也是一个道理的,只不过虚函数表指针会简单一些,因为在虚函数表前面,要么没有成员,要么肯定就是已经是4字节对齐了,不会出现像虚基类表指针那样的前面补几个字节后面补几个字节这种情况。比如下面那样:
#include"stdafx.h"
#pragma pack(8)
virtual void funA(){}
A() :a(0xaaaaaaaa){}
int _tmain(int argc, _TCHAR* argv[])
类A有虚函数,所以会有一个隐藏的成员,虚函数表指针,放在类对象的最开始的地方,因为类A的最大对齐参数就是隐藏成员本身的对齐参数4了,因隐藏成员的加入而引入的总共字节数就自然是它的整数倍了,所以就没必要再隐藏成员后面填充字节了,然后就是成员变量a紧紧跟着在后面,因为A本身也要自身对齐,按照它的成员中最大的那个有效对齐作为自己的自身对齐,也就是隐藏成员虚函数指针,所以A本身要按照4字节对齐,所以在a后面要填充3个字节才满足对齐规则。
#include"stdafx.h"
#pragma pack(8)
double c2;
virtual void funC(){}
C() :c(0xaaaaaaaa), c2(0){}
int _tmain(int argc, _TCHAR* argv[])
C类也是差不多,但是C有一个double成员,它的有效对齐是8字节的,是C中各个成员最大的一个,所以隐藏成员引入的字节要是8的整数倍。所以虚函数指针要至少填充4个字节然后加上它本身的4字节才能满足要求。
看过本文的人也看了:
我要留言技术领域:
取消收藏确定要取消收藏吗?
删除图谱提示你保存在该图谱下的知识内容也会被删除,建议你先将内容移到其他图谱中。你确定要删除知识图谱及其内容吗?
删除节点提示无法删除该知识节点,因该节点下仍保存有相关知识内容!
删除节点提示你确定要删除该知识节点吗?C++&虚基类的定义、功能、规定(转)
虚继承和虚基类的定义是非常的简单的,同时也是非常容易判断一个继承是否是虚继承的,虽然这两个概念的定义是非常的简单明确的,但是在C++语言中虚继承作为一个比较生僻的但是又是绝对必要的组成部份而存在着,并且其行为和模型均表现出和一般的继承体系之间的巨大的差异(包括访问性能上的差异),现在我们就来彻底的从语言、模型、性能和应用等多个方面对虚继承和虚基类进行研究。
&首先还是先给出虚继承和虚基类的定义。
虚继承:在继承定义中包含了virtual关键字的继承关系;
虚基类:在虚继承体系中的通过virtual继承而来的基类,需要注意的是:struct CSubClass : public
virtual CBase {};
其中CBase称之为CSubClass的虚基类,而不是说CBase就是个虚基类,因为CBase还可以不不是虚继承体系中的基类。
有了上面的定义后,就可以开始虚继承和虚基类的本质研究了,下面按照语法、语义、模型、性能和应用五个方面进行全面的描述。
语法有语言的本身的定义所决定,总体上来说非常的简单,如下:
&&&&&&&&&&
struct CSubClass : public virtual CBaseClass {};
其中可以采用public、protected、private三种不同的继承关键字进行修饰,只要确保包含virtual就可以了,这样一来就形成了虚继承体系,同时CBaseClass就成为了CSubClass的虚基类了。其实并没有那么的简单,如果出现虚继承体系的进一步继承会出现什么样的状况呢?
如下所示:
&&&&&&&&&&&
&&&&&&&&&&&
struct CBaseClass1
&&&&&&&&&&&
&&&&&&&&&&&&&&&
CBaseClass1( size_t i ) : m_val( i ) {}
&&&&&&&&&&&&&&&
&&&&&&&&&&&
&&&&&&&&&&&&
&&&&&&&&&&&
struct CSubClassV1 : public virtual CBaseClass1
&&&&&&&&&&&
&&&&&&&&&&&&&&&
CSubClassV1( size_t i ) : CBaseClass1( i ) {}
&&&&&&&&&&&
&&&&&&&&&&&
struct CSubClassV2 : public virtual CBaseClass1
&&&&&&&&&&&
&&&&&&&&&&&&&&&
CSubClassV2( size_t i ) : CBaseClass1( i ) {}
&&&&&&&&&&&
&&&&&&&&&&&
struct CDiamondClass1 : public CSubClassV1, public
CSubClassV2
&&&&&&&&&&&
&&&&&&&&&&&&&&&
CDiamondClass1( size_t i ) : CBaseClass1( i ), CSubClassV1( i ),
CSubClassV2( i ) {}
&&&&&&&&&&&
&&&&&&&&&&&
&&&&&&&&&&&
struct CDiamondSubClass1 : public CDiamondClass1
&&&&&&&&&&&
&&&&&&&&&&&&&&&
CDiamondSubClass1( size_t i ) : CBaseClass1( i ), CDiamondClass1( i
&&&&&&&&&&&
注意上面代码中的CDiamondClass1和CDiamondSubClass1两个类的构造函数初始化列表中的内容。可以发现其中均包含了虚基类CBaseClass1的初始化工作,如果没有这个初始化语句就会导致编译时错误,为什么会这样呢?一般情况下不是只要在CSubClassV1和CSubClassV2中包含初始化就可以了么?要解释该问题必须要明白虚继承的语义特征,所以参看下面语义部分的解释。
从语义上来讲什么是虚继承和虚基类呢?上面仅仅是从如何在C++语言中书写合法的虚继承类定义而已。
首先来了解一下virtual这个关键字在C++中的公共含义,在C++语言中仅仅有两个地方可以使用virtual这个关键字,一个就是类成员虚函数和这里所讨论的虚继承。不要看这两种应用场合好像没什么关系,其实他们在背景语义上具有virtual这个词所代表的共同的含义,所以才会在这两种场合使用相同的关键字。
那么virtual这个词的含义是什么呢?virtual在《美国传统词典[双解]》中是这样定义的:
&&&&&&&&&&
adj.(形容词)
&&&&&&&&&&
1. Existing or resulting in essence or effect though not in
actual&fact, form, or name:
&&&&&&&&&&&&&
实质上的,实际上的:虽然没有实际的事实、形式或名义,但在实际上或效果上存在或产生的;
&&&&&&&&&&
2. Existing in the mind, especially as a product of the
imagination.&Used in literary criticism of
&&&&&&&&&&&&&
虚的,内心的:在头脑中存在的,尤指意想的产物。用于文学批评中。
我们采用第一个定义,也就是说被virtual所修饰的事物或现象在本质上是存在的,但是没有直观的形式表现,无法直接描述或定义,需要通过其他的间接方式或手段才能够体现出其实际上的效果。
那么在C++中就是采用了这个词意,不可以在语言模型中直接调用或体现的,但是确实是存在可以被间接的方式进行调用或体现的。比如:虚函数必须要通过一种间接的运行时(而不是编译时)机制才能够激活(调用)的函数,而虚继承也是必须在运行时才能够进行定位访问的一种体制。存在,但间接。其中关键就在于存在、间接和共享这三种特征。
对于虚函数而言,这三个特征是很好理解的,间接性表明了他必须在运行时根据实际的对象来完成函数寻址(C++中利用类指针*与引用&来完成多样性的重载),共享性表象在基类会共享被子类重载后的虚函数,其实指向相同的函数入口。
对于虚继承而言,这三个特征如何理解呢?存在即表示虚继承体系和虚基类确实存在,间接性表明了在访问虚基类的成员时同样也必须通过某种间接机制来完成(下面模型中会讲到),共享性表象在虚基类会在虚继承体系中被共享,而不会出现多份拷贝。
那现在可以解释语法小节中留下来的那个问题了,“为什么一旦出现了虚基类,就必须在每一个继承类中都必须包含虚基类的初始化语句”。
由上面的分析可以知道,虚基类是被共享的,也就是在继承体系中无论被继承多少次,对象内存模型中均只会出现一个虚基类的子对象(这和多继承是完全不同的),这样一来既然是共享的那么每一个子类都不会独占,但是总还是必须要有一个类来完成基类的初始化过程(因为所有的对象都必须被初始化,哪怕是默认的),同时还不能够重复进行初始化,那到底谁应该负责完成初始化呢?C++标准中(也是很自然的)选择在每一次继承子类中都必须书写初始化语句(因为每一次继承子类可能都会用来定义对象),而在最下层继承子类中实际执行初始化过程。所以上面在每一个继承类中都要书写初始化语句,但是在创建对象时,而仅仅会在创建对象用的类构造函数中实际的执行初始化语句,其他的初始化语句都会被压制不调用。
为了实现上面所说的三种语义含义,在考虑对象的实现模型(也就是内存模型)时就很自然了。在C++中对象实际上就是一个连续的地址空间的语义代表,我们来分析虚继承下的内存模型。
&&&&&&&&&&
也就是说在对象内存中必须要包含虚基类的完整子对象,以便能够完成通过地址完成对象的标识。那么至于虚基类的子对象会存放在对象的那个位置(头、中间、尾部)则由各个编译器选择,没有差别。(在VC8中无论虚基类被声明在什么位置,虚基类的子对象都会被放置在对象内存的尾部)
&&&&&&&&&&
间接性表明了在直接虚基承子类中一定包含了某种指针(偏移或表格)来完成通过子类访问虚基类子对象(或成员)的间接手段(因为虚基类子对象是共享的,没有确定关系),至于采用何种手段由编译器选择。(在VC8中在子类中放置了一个虚基类指针vbc,该指针指向虚函数表中的一个slot,该slot中存放着虚基类子对象的偏移量的负值,实际上就是个以补码表示的int类型的值,在计算虚基类子对象首地址时,需要将该偏移量取绝对值相加,这个主要是为了和虚表中只能存放虚函数地址这一要求相区别,因为地址是原码表示的无符号int类型的值)
&&&&&&&&&&
共享表明了在对象的内存空间中仅仅能够包含一份虚基类的子对象,并且通过某种间接的机制来完成共享的引用关系。在介绍完整个内容后会附上测试代码,体现这些内容。
由于有了间接性和共享性两个特征,所以决定了虚继承体系下的对象在访问时必然会在时间和空间上与一般情况有较大不同。
&&&&&&&&&&
在通过继承类对象访问虚基类对象中的成员(包括数据成员和函数成员)时,都必须通过某种间接引用来完成,这样会增加引用寻址时间(就和虚函数一样),其实就是调整this指针以指向虚基类对象,只不过这个调整是运行时间接完成的。
&&&&&&&&&&
(在VC8中通过打开汇编输出,可以查看*.cod文件中的内容,在访问虚基类对象成员时会形成三条mov间接寻址语句,而在访问一般继承类对象时仅仅只有一条mov常量直接寻址语句)
&&&&&&&&&&
由于共享所以不同在对象内存中保存多份虚基类子对象的拷贝,这样较之多继承节省空间。
谈了那么多语言特性和内容,那么在什么情况下需要使用虚继承,而一般应该如何使用呢?
这个问题其实很难有答案,一般情况下如果你确性出现多继承没有必要,必须要共享基类子对象的时候可以考虑采用虚继承关系(C++标准ios体系就是这样的)。由于每&一个继承类都必须包含初始化语句而又仅仅只在最底层子类中调用,这样可能就会使得某些上层子类得到的虚基类子对象的状态不是自己所期望的(因为自己的初始化语句被压制了),所以一般建议不要在虚基类中包含任何数据成员(不要有状态),只可以作为接口类来提供。
已投稿到:
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。博客访问: 504464
博文数量: 159
博客积分: 6010
博客等级: 准将
技术积分: 1640
注册时间:
IT168企业级官微
微信号:IT168qiye
系统架构师大会
微信号:SACC2013
分类: C/C++
刚刚有同学问到虚基类,自己的第一印象竟然是包含纯虚函数的类。立刻有觉得的不对,遂整理的一下,希望不会再犯这样的错误。
目的:为了引起晚绑定
实现方法:在声明函数时使用virtual关键字
纯虚函数:
目的:提供抽象的接口
实现方法:virtual int foo()=0;
特点:如果一个类中包含纯虚函数,则该类的VTABLE是不完整的,故而无法生成该类的对象。这样的类称为纯抽象基类
纯抽象基类:
目的:提供抽象的接口
实现方法:包含纯虚函数
特点:不能生成该类的对象
目的:为了消除二义性
实现方法:virtual继承
特点:一个类既可以作为某个类的虚基类,也可以作为另一个类的非虚基类。
阅读(1101) | 评论(1) | 转发(0) |
相关热门文章
给主人留下些什么吧!~~
hehe,要点懂了!!
请登录后评论。C++ 虚函数&纯虚函数&抽象类&接口&虚基类 [3]_博客园
当前位置: >
>C++ 虚函数&纯虚函数&抽象类&接口&虚基类
C++ 虚函数&纯虚函数&抽象类&接口&虚基类
& 作者:fly1988happy & 来源: 博客园-fly1988happy &
用的函数。
3. 纯虚函数
许多情况下,在基类中不能对虚函数给出有意义的实现,则把它声明为纯虚函数,它的实现留给该基类的派生类去做。
纯虚函数的声明格式:virtual &函数返回类型说明符& &函数名& ( &参数表& )=0;
纯虚函数的作用是为派生类提供一个一致的接口。
4.抽象类(abstract class)
抽象类是指含有纯虚函数的类(至少有一个纯虚函数),该类不能创建对象(抽象类不能实例化),但是可以声明指针和引用,用于基础类的接口声明和运行时的多态。
抽象类中,既可以有抽象方法,也可以有具体方法或者叫非抽象方法。抽象类中,既可以全是抽象方法,也可以全是非抽象方法。一个继承于抽象类的子类,只有实现了父类所有的抽象方法才能够是非抽象类。
接口是一个概念。它在C++中用抽象类来实现,在C#和Java中用interface来实现。
接口是专门被继承的。接口存在的意义也是被继承。和C++里的抽象类里的纯虚函数是相同的。不能被实例化。定义接口的关键字是interface,例如:   public interface MyInterface{   public void add(int x,int y);   public void volume(int x,int y,int z);   }  
继承接口的关键字是implements,相当于继承类的extends。需要注意的是,当继承一个接口时,接口里的所有函数必须全部被覆盖。当想继承多个类时,开发程序不允许,报错。这样就要用到接口。因为接口允许多重继承,而类不允许(C++中可以多重继承)。所以就要用到接口。
在派生类继承基类时,加上一个virtual关键词则为虚拟基类继承,如:class derive : virtual public base{};
虚基类是相对于它的派生类而言的,它本身可以是一个普通的类。只有它的派生类虚继承它的时候,它才称作虚基类,如果没有虚继承的话,就称为基类。比如类B虚继承于类A,那类A就称作类B的虚基类,如果没有虚继承,那类B就只是类A的基类。虚继承主要用于一个类继承多个类的情况,避免重复继承同一个类两次或多次。例如 由类A派生类B和类C,类D又同时继承类B和类C,这时候类D就要用虚继承的方式避免重复继承类A两次。
7. 抽象类VS接口
一个类可以有多个接口,只能继承一个父类??
抽象类可以有构造方法,接口中不能有构造方法;
抽象类中可以有普通成员变量,接口中没有普通成员变量;
接口里边全部方法都必须是abstract的,抽象类的可以有实现了的方法;
抽象类中的抽象方法的访问类型可以是public,protected,但接口中的抽象方法只能是public类型的,并且默认即为public abstract类型;
抽象类中可以包含静态方法,接口中不能包含静态方法;
抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是public static final类型,并且默认即为public static final类型。
8. 虚函数VS纯虚函数
虚函数引入原因:为了方便使用多态特性,我们常常需要在基类中定义虚函数。纯虚函数引入原因:1)同“虚函数”;2)在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。纯虚函数就是基类只定义了函数体,没有实现过程。纯虚函数相当于接口,不能直接实例话,需要派生类来实现函数定义;有的人可能在想,定义这些有什么用?比如你想描述一些事物的属性给别人,而自己不想去实现,就可以定义为纯虚函数。说的再透彻一些,比如盖楼房,你是老板,你给建筑公司描述清楚你的楼房的特性,多少层,楼顶要有个花园什么的,建筑公司就可以按照你的方法去实现了,如果你不说清楚这些,可能建筑公司不太了解你需要楼房的特性。用纯需函数就可以很好的分工合作了。
二者的区别:
1& 类里声明为虚函数的话,这个函数是实现的,哪怕是空实现,它的作用就是为了能让这个函数在它的子类里面可以被重载,这样的话,编译器就可以使用后期绑定来达到多态了;纯虚函数只是一个接口,是个函数的声明而已,它要留到子类里去实现。
2&虚函数在子类里面也可以不重载的;但纯虚必须在子类去实现,这就像Java的接口一样。通常我们把很多函数加上virtual,是一个好的习惯,虽然牺牲了一些性能,但是增加了面向对象的多态性,因为你很难预料到父类里面的这个函数不在子类里面不去修改它的实现;
3&虚函数的类用于“实作继承”,继承接口的同时也继承了父类的实现。当然我们也可以完成自己的实现。纯虚函数的类用于“介面继承”,主要用于通信协议方面。关注的是接口的统一性,实现由子类完成。一般来说,介面类中只有纯虚函数的;
4&带纯虚函数的类叫抽象类,这种基类不能直接生成对象,而只有被继承,并重写其虚函数后,才能使用。
相关阅读:
来源:(微信/QQ:,微信公众号:makaidong-com) &&&&&& 欢迎分享本文,转载请保留出处!
&&&&&& 【原文阅读】:
上一篇:没有了
【相关文章】
每日最新文章
每日最热文章
本周最热文章
本月最热文章
本年最热文章
Powered by
Copyright &
www.makaidong.com, All Rights Reserved}

我要回帖

更多关于 设置虚基类的目的是 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信