虚基类相关总结
尝试性熟悉熟悉写法,总结了虚基类的知识,内容有不足和错误还望大神指正
虚基类:
虚基类继承
1.虚基类的作用:
(1):当在多条继承路径上有一个公共的基类,在这些路径的某几条汇合处,这个公共的基类就会产生多个实例(或多个副本),若只想保存这个基类的一个实例,可以将这个公共基类说明为虚基类,示例如下:
class CBase { };
class ChildA1:virtual public CBase{ };
class ChildA2:virtual public CBase{ };
class ChildB:public ChildA1,ChildA2{ };
则在类ChildB的对象中,仅有类CBase的一份对象数据
(2):虚基类的初始化是由派生类调用虚基类构造函数完成。例如
class A//定义基类A
{
A(int i){ } //基类构造函数,有一个参数
};
class B :virtual public A //A作为B的虚基类
{
B(int n):A(n){ } //B类构造函数,在初始化表中对虚基类初始化
};
class C :virtual public A //A作为C的虚基类
{
C(int n):A(n){ }
//C类构造函数,在初始化表中对虚基类初始化
};
class D :public B,public C
//类D的构造函数,在初始化表中对直接基类和虚基类初始化
{
D(int n):A(n),B(n),C(n){ }
};
注意:
在定义类D的构造函数时,与以往使用的方法有所不同。规定:
在最后的派生类中不仅要负责对其直接基类进行初始化,还要负责对虚基类初始化。C++编译系统只执行最末端的派生类对虚基类的构造函数的调用,而忽略虚基类的其他派生类(如类B和类C)
对虚基类的构造函数的调用仅由最末端派生类完成,这就保证了虚基类的数据成员不会被多次初始化。很多时候,对于继承链上的中间类,我们也会在其构造函数中显式调用虚基类的构造函数,因为一旦需要创建这些中间类的对象,也要保证它们得到正确的初始化。
虚基类的特点:
(1)虚基类构造函数必须由最新派生出来的类负责调用(即使存在中间基类).
(2)虚基类的构造函数先于非虚基类的构造函数执行。
派生类构造函数的执行顺序
派生类构造函数的执行顺序:
1.调用基类的构造函数,如有多个基类,则按照它们被继承的顺序依次调用。
2.调用内嵌对象的构造函数,如果有多个,则按照它们在类的数据成员声明中的先后顺序依次调用。
3.执行派生类的构造函数体中的内容。
如果派生类的构造函数没有显示声明其基类和其内嵌对象的构造方式,那么系统按照“默认”方式对它们进行初始化,也就是调用它们的默认构造函数。析构函数的执行顺序与构造函数的顺序正好相反。
引入虚基类和直接继承会有什么区别
由于有了间接性和共享性两个特征,所以决定了虚继承体系下的对象在访问时必然会在时间和空间上与一般情况有较大不同。
1.时间:在通过继承类对象访问虚基类对象中的成员(包括数据成员和函数成员)时,都必须通过某种间接引用来完成,这样会增加引用寻址时间(就和虚函数一样),其实就是调整this指针以指向虚基类对象,只不过这个调整是运行时间接完成的。
2.空间:由于共享所以不必要在对象内存中保存多份虚基类子对象的拷贝,这样较之多继承节省空间。虚拟继承与普通继承不同的是,虚拟继承可以防止出现diamond继承时,一个派生类中同时出现了两个基类的子对象。也就是说,为了保证这一点,在虚拟继承情况下,基类子对象的布局是不同于普通继承的。因此,它需要多出一个指向基类子对象的指针。
多级virtual问题
请仔细阅读下面代码,将以此展开对多级virtual问题的讨论:
class A {
protected:
int a;
public:
A(int a) :a(a) {}
A() :a(0) {}
};
class B:virtual public A {
protected:
int b;
public:
B(int a,int b):A(a),b(b){}
B() :A(), b(0) {}
};
class C :virtual public B {
protected:
int c;
public:
C(int a,int b,int c):A(a),B(a,b),c(c){}
C() :A(), B(), c(0) {}
};
class D :virtual public C {
protected:
int d;
public:
D(int a, int b, int c, int d):A(a), B(a, b), C(a, b, c), d(d) {}
D():A(),B(),C(),d(0){}
};
若将class D中构造函数初始化列表中A,B,C任意一个构造函数删去,均不能完成对其相应属性的初始化。
例如将:
D(int a, int b, int c, int d):A(a), B(a, b), C(a, b, c), d(d) {}
改为:
D(int a, int b, int c, int d):A(a), C(a, b, c), d(d) {}
便不能完成对class B中属性b的初始化,但是如果把class C:virtual public B
中的“virtual”关键字去掉,便能完成对class B中属性b的初始化。
原因便在于如果class C把class B声明为虚基类时,class D初始化时,如果不调用class B的构造函数,则系统会调用class B的默认构造函数,因为C++编译系统只执行最后的派生类对虚基类的构造函数的调用,而忽略虚基类的其他派生类(如类B和类C) 。而将“virtual”关键字去掉后,class C把class B声明为基类,class D初始化时,可以通过调用class C的构造函数再调用class B的构造函数,对class B完成初始化。
如果多重继承中类的继承均采用虚基类,那么第二级以及之后的派生类,如果不显式调用虚基类的构造函数,则系统调用默认构造函数,可能导致虚基类无法按预期方式初始化。因此,可将多重继承中第二级以及之后的部分派生类的继承中采用直接继承,既能简化程序,又能保证类的初始化按预期进行。
使用注意
(1) 一个类可以在一个类族中既被用作虚基类,也被用作非虚基类。
(2) 在派生类的对象中,同名的虚基类只产生一个虚基类子对象,而某个非虚基类产生各自的子对象。
(3) 虚基类子对象是由最远派生类的构造函数通过调用虚基类的构造函数进行初始化的。
(4) 最远派生类是指在继承结构中建立对象时所指定的类。
(5) 派生类的构造函数的成员初始化列表中必须列出对虚基类构造函数的调用;如果未列出,则表示使用该虚基类的缺省构造函数。
(6) 从虚基类直接或间接派生的派生类中的构造函数的成员初始化列表中都要列出对虚基类构造函数的调用。但仅仅用建立对象的最远派生类的构造函数调用虚基类的构造函数,而该派生类的所有基类中列出的对虚基类的构造函数的调用在执行中被忽略,从而保证对虚基类子对象只初始化一次。
(7) 在一个成员初始化列表中同时出现对虚基类和非虚基类构造函数的调用时,虚基类的构造函数先于非虚基类的构造函数执行。
上一篇: C# MVC项目中添加定时器
下一篇: STM32定时器控制PWM输出