-
2020-12-19 22:01:26
今天专门看了一下虚继承的东西,以前都没怎么用过,具体如下:
父类:
复制代码代码如下:
class CParent
{
....
};
继承类的声明比较特别:class CChild : virtual public CParent
{
....
}
请问,这个"virtual"是什么作用及含义?---------------------------------------------------------------
表示虚拟继承,和普通继承是C++的两种继承方式。
例如B1、B2 继承A 而C多重继承B1、B2
如果普通继承则C包含两份A的拷贝,分别来自于B1、B2
而虚拟继承则只包含一份A的拷贝
---------------------------------------------------------------
这个"virtual"是什么作用及含义?证明这个CParent是CChild 的虚基类
虚基类 的 作用
虚基类是指:class SubClass : virtual public BaseClass 中以virtual声明的基类!!由于C++支持多重继承,所以对于一个派生类中有几个直接父类,而几个直接父类中有几个可能分别继承自某一个基类(就是父类的父类),这样在构造最终派生类时,会出现最终派生类中含有多个同一个基类的情况,就会产生二义性的问题(不知道该调用哪个基类的成员变量和函数),为解决此问题,需要使用虚基类,即只对此基类生成一块内存区域,这样最终派生类中就只会含有一个基类了
典型的需要用虚基类的情况如下: A
/ \
B C
\ /
D
其中D继承自BC,BC分别继承自A,所以A要分别被BC虚拟继承
程序嘛…………
复制代码代码如下:
class A {
public:
void printA() {cout<
};
class B:virtual public A;
class C:virtual public A;
class D:public B,public C;
这样在D构造出来后,它的存储区域中只有一个A,不会有二义性问题
比如:D d=new D;
此时若使用d.printA();不会有问题;但若B和C不是虚继承自A,就会有二义性问题
COM中 多重继承不采用虚拟继承, 否则会导致与COM不兼容的vtbl。若 IX 和 IY 按照虚拟继承方式继承IUnknown,那么 IX 和 IY 的vtbl中的头三个函数指向的将不是IUnknown的三个成员函数(详见Com技术内幕)
IUnknown -> IX
---> CA
IUnknown -> IY
通常将一种类型的指针转换成另外一种类型的指针并不会改变指针的值,但为了支持多重继承,在某些情况下,C++必须改变类指针的值。COM中的多接口继承就是如此,此时
CA* pA = new CA();
IY * pc = pA; 会被编译器改成 IY * pC = (char * )pA + deltaIY(一个偏移量);
基类的析构函数也应该定义成virtual才会在继承类的实例析构时被调用以清除基类用到的资源。
更多相关内容 -
详细讲解虚继承
2021-06-22 21:48:37虚继承的作用:防止产生二义性 先来看一看下面的一段代码吧: #define _CRT_SECURE_NO_WARINGS #include<iostream> #include<string> using namespace std; class Grandfather { public: ...虚继承的作用:防止产生二义性
先来看一看下面的一段代码吧:
#define _CRT_SECURE_NO_WARINGS #include<iostream> #include<string> using namespace std; class Grandfather { public: Grandfather() { cout << "爷爷的构造函数" << endl; } ~Grandfather() { cout << "爷爷的析构函数" << endl; } }; class Father :public Grandfather { public: Father() { cout << "爸爸的构造函数" << endl; } ~Father() { cout << "爸爸的析构函数"<<endl; } }; class Mother :public Grandfather { public: Mother() { cout << "妈妈的构造函数" << endl; } ~Mother() { cout << "妈妈的析构函数" << endl; } }; class Children :public Father, public Mother { public: Children() { cout << "儿子的构造函数" << endl; } ~Children() { cout << "儿子的析构函数" << endl; } }; int main() { Children me; return 0; }
下面我们再来看一看他的输出结果:
现在我们对运行结果进行分析不难得出:执行Children时必须先执行Father类(因为Children公有继承了Father)但是往前面找到Father类我们又发现它公有继承了Grandfather类,因此最先执行的是Grandfather的构造函数,再执行Father和Mother的构造函数,最后执行自己的构造函数。析构函数的话与构造函数的顺序相反。
在这条代码的输出结果中我们发现,Children继承了两次爷爷类,这无疑会增加程序的运行时间,如果我们只想继承一次爷爷类就要使用到虚继承的知识。虚继承可以使子类只继承一次父类的父类。实现方法很简单:就是在Father和Mother继承爷爷类之前加一个virtual即可
代码:
#define _CRT_SECURE_NO_WARINGS #include<iostream> #include<string> using namespace std; class Grandfather { public: Grandfather() { cout << "爷爷的构造函数" << endl; } ~Grandfather() { cout << "爷爷的析构函数" << endl; } }; class Father :virtual public Grandfather { public: Father() { cout << "爸爸的构造函数" << endl; } ~Father() { cout << "爸爸的析构函数"<<endl; } }; class Mother :virtual public Grandfather { public: Mother() { cout << "妈妈的构造函数" << endl; } ~Mother() { cout << "妈妈的析构函数" << endl; } }; class Children :public Father, public Mother { public: Children() { cout << "儿子的构造函数" << endl; } ~Children() { cout << "儿子的析构函数" << endl; } }; int main() { Children me; return 0; }
运行结果:
为什么析构函数必须是虚函数
首先我们先来复习一下
多态的三要素:
一、要有子类继承父类
二、要有虚函数
三、父类指针指向对象
来看看这个代码:
#include<iostream> using namespace std; class Father { public: ~Father() { cout << "父类的析构函数" << endl; } }; class Son :public Father { public: ~Son() { cout << "子类析构函数"; } }; int main() { Father* p = new Son; delete p; return 0; }
他的运行结果:
从运行结果我们可以发现:子类的析构函数没有被调用,子类的资源内存没有被释放,这会造成内存泄漏,解决方法很简单,我们把Father类的析构函数写成虚函数即可
#include<iostream> using namespace std; class Father { public: virtual ~Father() { cout << "父类的析构函数" << endl; } }; class Son :public Father { public: ~Son() { cout << "子类析构函数"<<endl; } }; int main() { Father* p = new Son; delete p; return 0; }
模板类的继承(继承子类的所有属性)
继承的遗传性(继承基类的所有属性)
#include<iostream> using namespace std; class A { public: A(int a):a(a){} int a; }; class B:public A { public: //B需要调用A的构造函数A() B(int a,int b):b(b),A(a){} //int a; 有a的属性 int b; }; class C :public B { public: //C需要调用B的构造函数但是不需要调用A的构造函数 C(int a,int b,int c):c(c),B(a,b){} //int a ,b;有a b 的属性 int c; }; class D :public C { public: //D只需要调用C的构造函数 D(int a,int b,int c,int d):d(d),C(a,b,c){} //int a,b,c;有a b c的属性 int d; void print() { cout << "a= " << a << " b= " << b << " c= " << c << " d= " << d << endl; } }; int main() { cout << "I Love You" << endl; D object(1, 2, 3, 4); object.print(); return 0; }
输出:
I Love You
a= 1 b= 2 c= 3 d= 4214135
-
C++虚继承的意义和使用
2017-10-15 11:05:19虚继承:多个派生类保存相同基类的同名成员时,虽可以在不同的数据成员中分别存放不同的数据 ,但我们只需要相同的一份。 解决了多父类重复成员只保留一份的问题。 比如现在有一个沙发床,它既有床的属性又有...虚继承:多个派生类保存相同基类的同名成员时,虽可以在不同的数据成员中分别存放不同的数据 ,但我们只需要相同的一份。
解决了多父类重复成员只保留一份的问题。
比如现在有一个沙发床,它既有床的属性又有沙发的属性,它们都有长宽高的属性,但是我们却只需要知道它的一个状态的属性。
它的一个长宽高能体现多个状态。
家具{
属性:长,宽,高;
}
沙发床:public 沙发,public 床{
属性:长,宽,高;
}
沙发:public 家具 ----》改为:沙发:virtual public 家具
{
属性:长,宽,高;
}
床:public 家具 ----》改为:床:virtual public 家具
{
属性:长,宽,高;
}
我们只需要他们的共同属性长宽高就行了,我们可以把共同属性提出来作为家具类,再由不同状态继承(虚继承),但最后都归总于沙发床。
#include <iostream> using namespace std; class Base{ public: Base(int d=100):data(d){ cout<<"Base()"<<data<<endl; } int data; }; class A :virtual public Base{ public: A():Base(){ cout<<"A()"<<data<<endl; } void setD(int d) { data = d; } }; class B :virtual public Base{ public: B():Base(){ cout<<"B()"<<data<<endl; } int getD() { return data; } }; class C :public A,public B{ public : C():A(),B(),Base(100){ cout<<"C()"<<data<<endl; } void dis(){ cout<<data<<endl; cout<<A::data<<endl; cout<<B::data<<endl; } }; int main(){ C c; c.dis(); c.setD(10000); c.setD(2000); cout<<c.getD()<<endl; c.dis(); }
更改床、沙发、沙发床的长宽高它们的不同状态的长宽高都会发生变化。
-
虚函数,虚继承,纯函数继承
2019-04-23 20:50:49覆盖就是子类会覆盖与父类相同的虚函数,下来看一个覆盖的代码: class A { public: void virtual f() { cout << "A" << endl; } }; class B :public A { public: void virtual f() { cout ...覆盖
覆盖就是子类会覆盖与父类相同的虚函数,下来看一个覆盖的代码:
class A { public: void virtual f() { cout << "A" << endl; } }; class B :public A { public: void virtual f() { cout << "B" << endl; } }; int main() { A* pa = new A(); pa->f(); //A B* pb = (B*)pa; pa->f(); // A delete pa, pb; pa = new B(); pa->f(); //B pb = (B*)pa; pb->f(); //B return 0; }
这是一个虚函数覆盖虚函数的问题,A中f是一个虚函数,虚函数是被子类同名函数所覆盖的。送一A类中的f函数会被B类中的f函数覆盖。很明显第一个输出是输出“A”,但是在 B* pb = (B*)pa;里面,该语句的意思是转化pa为B类型并新建一个pb指针,将pa赋值到pb,所以pa在这里是没有发生任何变化的,所以第二个pa->f也指向的是“A”。
delete pa,pb删除了pa,pb指向的地址,但pa,pb指针并没有被删除,这就是野指针(悬浮指针),现在重新指向B类,而pa指针类型是A类的,父类的指针指向子类的对象,在这里就会发生覆盖。第三个打印的是“B”.pb=(B*)pa;转化pa为B类指针给pb赋值,但pa所指向的f函数是B类的f函数,所以pb所指向的f函数是B类的f函数,打印“B”。
虚继承
虚继承是多重继承中特有的概念,虚基类是为了解决多重继承而出现的。例如B继承A,C继承A,D同时继承BC,因此出现了交叉继承,在D类中会出现两个A,为了节省空间,可以将B,C对A的继承定义为虚拟继承,而A就成了虚基类,这就是所谓的菱形继承。
class A; class B :public virtual A; class C :public virtual A; class D :public B, public C;
虚函数继承
下面看一段代码:
class A { public: virtual void a() {} }; class B :public virtual A { public: virtual void b() {} }; class C:public virtual A { public: virtual void c() {} }; int main() { cout << sizeof(A) << endl; // 4 cout << sizeof(B) << endl; // 12 cout << sizeof(C) << endl; // 20 return 0; }
A类中有一个虚函数,所以必须有一个对应的虚函数表来记录对应的函数入地址。所以有虚函数的类生成对象的前四个字节是一个指针,指向的是虚函数生成的虚函数表,所以A类的大小是4个字节。
对于B类,虚继承了A类,同时还拥有自己的系函数,那么B类有一个B类vfptr指针指向自己的虚函数表,这里大小是4个字节。可虚继承需要通过加入一个虚类指针记为vfptr_B_A来指向父类,这里大小也是4个字节,同时还有继承父类的所有内容sizeof(A)是4个字节大小,所以B类的大小是12个字节。
C类的话也有一个自己的vfptr_C指向自己的虚函数表,然后是一个虚类指针vfptr_C_B_A,同时还继承了B类的所有内容,所以大小是12字节+4+4 = 20字节。
这就是虚函数继承,虚继承不仅继承了所有父类内容,还有一个虚类指针来指向父类。这里的大小是在gcc编译下的结果,如果父类有一个指向自己虚函数的指针,会和子类共享这个父类的虚函数表的空间,不会占用子类的内存。在VC条件下编译不共享这个虚函数指针空间
纯虚函数
看下面代码有什么问题?
class A { public: A() {} ~A() {} virtual void f() = 0; }; int main() { A a; return 0; }
这个代码的问题出在A a;这里,因为A类中的f函数是一个纯虚函数,所以A类不能实例化对象。解决方法是将f()函数修改成一般函数。
-
虚继承的偏移量问题
2017-11-13 16:08:44使用虚继承,比起单继承和多重继承有更大的实现开销、调用开销。回忆一下,在单继承和多重继承的情况下,内嵌的基类实例地址比起派生类实例地址来,要么地址相同(单继承,以及多重继承的最靠左基类) ,要么地址... -
Th3.15:继承的构造函数、多重继承、虚继承之详述
2021-11-21 14:18:39本小节的知识点分别是继承的构造函数、多重继承、虚继承。 今天总结的知识分为以下5个点: (1)继承的构造函数 (2)多重继承 (2.1)多重继承概述 (2.2)静态成员变量 (2.3)派生类构造函数与析构函数 ... -
C++ 虚继承的对象模型
2016-06-17 17:24:07我们知道,虚继承的基类在类的层次结构中只可能出现一个实例。虚基类在类的层次结构中的位置是不能固定的,因为继承了虚基类的类可能会再次被其他类多继承。 比如class A: virtual T{} 这时T的位置如果相对于A是... -
C++虚函数和虚继承浅析
2016-10-24 10:50:40来源:http://www.cnblogs.com/xien7/archive/2013/03/12/2954364.html本文针对C++里的虚函数,虚继承表现和原理进行一些简单分析,有不对的地方请指出。下面都是以VC2008编译器对这两种机制内部实现为例。1. 虚函数... -
单继承、多继承、菱形继承的虚函数表
2019-12-29 20:19:49最近被问到一个关于多继承虚函数表的问题,当时回答是可能存在多个虚函数表,应该是顺序排列的,但具体怎么排列还是有些疑惑的,回答的时候到有点儿心虚。之后查了资料,做了简单的实验,可以确定的是对于继承了多个... -
浅谈C++多态实现原理(虚继承的奥秘)
2018-12-24 17:47:24根据我的已有知识,如果要实现C++的多态,那么,基类中相应的函数必须被声明为虚函数(或纯虚函数)。举个例子: class Point { public: Point(float x = 0.0, float y = 0.0) : _x(x), _y(y) { } virtual ... -
C++ 虚继承内存对象模型
2020-01-09 11:08:38文章目录C++ 虚继承内存对象模型1. 普通虚继承1.1 内存大小1.2 内存布局1.3 指针赋值1.4 基类指针调用1.5 子类指针的调用2. 子类添加自己的虚函数2.1 虚函数表2.2 虚继承表2.3 内存布局3. 菱形继承4. 总结 C++ 虚... -
【C++】什么是多态?虚函数的底层实现原理|多重继承|菱形继承
2019-07-16 02:06:38在C++中,多态是利用虚函数来实现的。比如说,有如下代码: #include <iostream> using namespace std; class Animal { public: void Cry() { cout << "Animal cry!" << endl; } }; class ... -
继承中的二义性 虚继承 virtual关键字
2017-03-01 20:59:59乍听虚继承,吓倒很多人!! 或许很多人会认为这和虚函数有关,其实,几乎没有任何关系。它的出现,是为了克服继承中一个非常棘手的问题,也就是臭名昭著的菱形继承(二义性)问题。 二义性,也就是说,假如我们有... -
C++虚继承(六) --- 虚继承浅析
2016-03-18 17:38:53C++支持多重继承,这和现实生活很类似,任何一个物体都不可能单一的属于某一个类型。就像马,第一想到的就是它派生自动物这个基类,但是它在某系地方可不可以说也派生自交通工具这一个基类呢?所以C++的多重继承很... -
什么是继承,什么是多态,方法的重载和覆盖有何区别?
2019-10-29 17:07:43什么是继承,什么是多态,方法的重载和覆盖有何区别? 1、什么是继承? 继承是子类调用父类的属性和方法。在Java中通过extends关键字实现,格式为Class 子类 extends 父类{主体}。 子类不能继承父类... -
什么叫虚继承(虚拟继承)?如何消除继承中的二义性?
2012-03-01 19:09:53乍听虚继承,吓倒很多人!! 或许很多人会认为这和虚函数有关,其实,几乎没有任何关系。它的出现,是为了克服继承中一个非常棘手的问题,也就是臭名昭著的菱形继承(二义性)问题。 二义性,也就是说,假如我们有... -
MSVC和MinGW/GCC的菱形虚继承内存分布对比
2022-04-17 16:47:05了解GCC在菱形虚继承下的内存分布情况 -
C++学习:虚函数,纯虚函数(virtual),虚继承,虚析构函数
2017-06-25 11:21:57C++学习:虚函数,虚继承,纯虚函数(virtual)虚析构函数 虚函数 纯虚函数 虚析构函数 虚继承 简介 在java这种高级语言中,有abstract和interface这两个关键字.代表的是抽象类和接口,但是在C++这门语言中,并没有专属的... -
探讨C++中不能声明为虚函数的有哪些函数
2020-12-31 12:30:59普通函数(非成员函数)只能被overload,不能被override,声明为虚函数也没有什么意思,因此编译器会在编译时邦定函数。 多态的运行期行为体现在虚函数上,虚函数通过继承方式来体现出多态作用,顶层 函数不属于成员... -
虚函数继承、虚继承、虚析构函数、纯虚函数
2014-04-29 11:18:131、什么是虚函数继承? -
虚继承与虚基类的本质(介绍的非常详细)
2014-09-20 00:17:09虚继承与虚基类的本质 虚继承和虚基类的定义是非常的简单的,同时也是非常容易判断一个继承是否是虚继承 的,虽然这两个概念的定义是非常的简单明确的,但是在C++语言中虚继承作为一个比较生 僻的但是又是绝对... -
什么是虚函数
2018-12-16 15:36:00在面向对象的C++语言中,虚...什么是虚函数: 虚函数是指一个类中你希望重载的成员函数 ,当你用一个 基类指针或引用 指向一个继承类对象的时候,调用一个虚函数时, 实际调用的是继承类的版本。 ——摘自MSDN 1 #i... -
继承与接口---覆盖、虚继承
2015-10-12 10:49:30(2)成员变量的覆盖:子类覆盖的仅仅是继承来的那个成员变量,而并不改变原 来父类中的变量;!!!!!(3)构造函数从基类开始构造,各个类的同名变量没有形成覆盖,都是单独的变 量。子类调用就近原则,如果父类存在... -
虚函数、纯虚函数、虚继承——virtual关键字的小例子
2022-01-03 17:00:44文章目录虚函数纯虚函数虚继承 虚函数 虚函数是C++的运行时多态,可以在基类中将被重写的成员函数设置为虚函数。 虚函数的作用是:当通过基类的指针或引用调用该成员函数时,将根据指针指向的对象类型确定调用的函数... -
C++虚重载函数被继承时的一个问题
2022-03-26 15:08:43本文讲述虚的重载函数在被继承时遇到的一个问题。 -
什么是虚方法?
2017-04-07 11:28:24小编在看《大话设计模式》的时候,书中常常提到虚方法,尽管按照例子敲了代码,也对书上的内容咬文嚼字地读了几遍,终究还是不太了解虚方法究竟是什么。于是百度看了很多网友们的讲解。下面就来说一下小编对虚方法的... -
C++ 虚继承和虚函数同时存在的对象模型
2016-06-22 20:16:23如果说没有虚函数的虚继承只是一个噩梦的话,那么这里就是真正的炼狱。这个C++中最复杂的继承层次在VC上的实现其实我没有完全理解,摸爬滚打了一番也算得出了微软的实现方法吧,至于一些刁钻的实现方式我也想不到... -
深入理解C++面向对象机制(二)虚继承
2014-08-16 15:00:45深入理解C++面向对象机制(二)虚继承 零.声明 1.《深入理解C++面向对象机制》系列的博文是博主阅读《深度探索C++对象模型》之后的自我总结性质的文章。当然也希望这些文章能够帮助那些想深入了解C++的网友。 ...