虚函数VS虚拟继承:C++多重继承二义性破解与性能调优
目录
虚拟继承
虚函数 vs 虚拟继承
虚拟继承时派生类对象的构造和析构
效率分析
虚拟继承
虚函数 vs 虚拟继承
在虚函数机制(动态多态机制)中
1、虚函数是存在的;(存在)
2、通过间接的方式去访问;(间接)
3、通过基类的指针访问到派生类的函数,基类的指针共享了派生类的方法(共享)
(如果没有虚函数,当通过pbase指针去调用一个普通的成员函数,那么就不会通过虚函数指针和虚表,直接到程序代码区中找到该函数;有了虚函数,去找这个虚函数的方式就成了间接的方式)
虚拟继承同样使用virtual关键字(存在、间接、共享)
1、存在即表示虚继承体系和虚基类确实存在
2、间接性表现在当访问虚基类的成员时同样也必须通过某种间接机制来完成(通过虚基表来完成)
3、共享性表现在虚基类会在虚继承体系中被共享,而不会出现多份拷贝
(虚基类的说法,如果B类虚拟继承了A类,那么说A类是B类虚基类,因为A类还可以以非虚拟的方式派生其他类)
补充:
(1)虚拟继承的内存结构
(2)如果虚基类中包含了虚函数
(3)如果派生类中又定义了新的虚函数,会在内存中多出一个属于派生类的虚函数指针,指向一张新的虚表(VS的实现)
(4)带虚函数的菱形继承——虚拟继承方式(拔高,不要求一定掌握)
虚拟继承时派生类对象的构造和析构
中间派生类公有继承,顶层基类,在底层派生类时会创建多个顶层基类子对象在调用函数时会出现问题,他不会知道使用哪个去调用函数,所以需要虚拟继承。
如下菱形继承的结构中,中间层基类虚拟继承了顶层基类,注意底层派生类的构造函数
class A
{
public:A(double a): _a(a){cout << "A(double)" << endl;}~A(){cout << "~A()" << endl;}
private:double _a = 10;
};class B
: virtual public A
{
public:B(double a, double b): A(a), _b(b){cout << "B(double,double)" << endl;}~B(){ cout << "~B()" << endl; }
private:double _b;
};class C
: virtual public A
{
public:C(double a, double c): A(a), _c(c){cout << "C(double,double)" << endl;}~C(){ cout << "~C()" << endl; }
private:double _c;
};class D
: public B
, public C
{
public:D(double a,double b,double c,double d): A(a), B(a,b), C(a,c), _d(d){cout << "D(double * 4)" << endl;}~D(){ cout << "~D()" << endl; }
private:double _d;
};
在虚拟继承的结构中,最底层的派生类不仅需要显式调用中间层基类的构造函数,还要在初始化列表最开始调用顶层基类的构造函数。
——那么A类构造岂不是会调用3次?
并不会,有了A类的构造之后会压抑B、C构造时调用A类构造,A类构造只会调用一次。可以对照菱形继承的内存模型理解,D类对象中只有一份A类对象的内容。
对于析构函数,同样存在这样的压抑效果,D类析构执行完后,根据继承声明顺序的反序调用C类的析构函数,C的析构函数执行完后并没有自动调用A的析构函数,而是接下来调用B的析构函数,最后调用A的析构函数。
效率分析
多重继承和虚拟继承对象模型较单一继承复杂的对象模型,造成了成员访问低效率,表现在两个方面:对象构造时 vptr 的多次设定,以及 this 指针的调整。对于多种继承情况的效率比较如下: