昨天的两篇文章《C++之类的继承》和《C++之虚函数》带领我们认识了C++中的类的继承方法,以及多态的实现。今天我们来学习一下二者的结合体——虚拟继承和虚基类。
在讲解什么是虚拟继承之前,我们需要先普及一下子类和父类在内存中的关系。如图1所示,每一个子类在内存中都拥有父类内存的一份拷贝。
图1 子类和父类在内存中的关系
子类对象占用的内存始终会比父类对象占用的内存大,因为子类继承父类,也就继承了父类的方法接口,所以在子类对象中有一份父类的内存拷贝。
接下来说说单一继承和菱形继承。
1)单一继承是指每一个子类都只有一个父类,如图2所示。单一继承的内存关系如图3所示。
图2 单一继承 图3 单一继承的内存关系
2)菱形继承是指一个类的两个父类有共同的父类。如图4所示。菱形继承的内存关系如图5所示。
图4 菱形继承 图5 菱形继承的内存关系
当我们看到菱形继承的时候,是不是发现一点问题。第一,类C中包含类A的两份一模一样的拷贝。第二,类A的构造函数被重复调用了两次。如果读者不相信,可以试一试下面的代码。
程序的运行结果如下:#include <iostream>using namespace std;class A
{public:
A(){cout<<"This is A constructor."<<endl;}
~A(){cout<<"This is A destructor."<<endl;}
void speak(){cout<<"This is A speak!"<<endl;}};class B1: public A{public:
B1(){cout<<"This is B1 constructor."<<endl;}
~B1(){cout<<"This is B1 destructor."<<endl;}
};class B2: public A{public:
B2(){cout<<"This is B2 constructor."<<endl;}
~B2(){cout<<"This is B2 destructor."<<endl;}
};class C:public B1,public B2{public:
C(){cout<<"This is C constructor."<<endl;}
~C(){cout<<"This is C destructor."<<endl;}
};int main()
{C *c=new C();
//c->speak();
delete c;
return 0;
}
如果我们在代码中再添加如下代码:“c->speak();”,编译器还会报出错误。错误信息如下:
Error 1 error C2385: ambiguous access of 'speak' e:\XXX\main.cpp 38
这是由于类B1和类B2都有函数speak(),编译器不知道应该执行哪一个,此处产生了二义性。
综上所述,菱形继承主要是一下三个问题:
- 存储问题:占用了更多的存储空间。
- 效率问题:基类的构造(析构)函数被调用了多次。
- 编译问题:调用基类的成员函数会产生二义性。
现在,终于轮到虚拟继承出场了!虚拟继承其实很简单,就是在基类前面加上一个virtual关键字。修改之后的代码如下:
#include <iostream>using namespace std;class A
{public:
A(){cout<<"This is A constructor."<<endl;}
~A(){cout<<"This is A destructor."<<endl;}
void speak(){cout<<"This is A speak!"<<endl;}};class B1: public virtual A{public:
B1(){cout<<"This is B1 constructor."<<endl;}
~B1(){cout<<"This is B1 destructor."<<endl;}
};class B2: virtual public A{public:
B2(){cout<<"This is B2 constructor."<<endl;}
~B2(){cout<<"This is B2 destructor."<<endl;}
};class C:public B1,public B2{public:
C(){cout<<"This is C constructor."<<endl;}
~C(){cout<<"This is C destructor."<<endl;}
};int main()
{C *c=new C();
c->speak();delete c;
return 0;
}
注意关键字virtual的位置。这时我们再运行程序,得到的结果如下:
看到了吧,class A的构造函数只执行了一遍,而且speak()函数正常执行,没有产生二义性。忘了说一句,被虚拟继承的基类就是虚基类,例如上面程序中的class A。
今天虚拟继承和虚基类就总结到这,如有疑问,欢迎评论!
感谢:《C++编程关键路径——程序员求职指南》