C++虚基类详解(转)

我们知道,如果一个派生类有多个直接基类,而这些直接基类又有一个共同的基类,则在最终的派生类中会保留该间接共同基类数据成员的多份同名成员。在引用这些同名的成员时,必须在派生类对象名后增加直接基类名,以避免产生二义性,使其惟一地标识一个成员,如:
    c1.A::display( )

在一个类中保留间接共同基类的多份同名成员,虽然有时是有必要的,可以在不同的数据成员中分别存放不同的数据,也可以通过构造函数分别对它们进行初始化。但在大多数情况下,这种现象是人们不希望出现的。因为保留多份数据成员的拷贝,不仅占用较多的存储空间,还增加了访问这些成员时的困难,容易出错。而且在实际上,并不需要有多份拷贝。

C++提供虚基类(virtual base class)的方法,使得在继承间接共同基类时只保留一份成员。假设类D是类B和类C公用派生类,而类B和类C又是类A的派生类,如图11.21所示。 设类A有数据成员data和成员函数fun;派生类B和C分别从类A继承了data和fun,此外类B还增加了自己的数据成员data_b,类C增加了数据成员data_c。如果不用虚基类,就会在类D中保留了类A成员data的两份拷贝,分别表示为int B::data和int C::data。同样有两个同名的成员函数,表示为void B::fun()和void C::fun()。类B中增加的成员data_b和类C中增加的成员dat_c不同名,不必用类名限定。此外,类D还增加了自己的数据成员data_d和成员函数fun_d。


图 11.21

现在,将类A声明为虚基类,方法如下:

  1. class A //声明基类A
  2. {
  3. // 代码
  4. };
  5. class B: virtual public A //声明类B是类A的公用派生类,A是B的虚基类
  6. {
  7. // 代码
  8. };
  9. class C: virtual public A //声明类C是类A的公用派生类,A是C的虚基类
  10. {
  11. // 代码
  12. };

注意: 虚基类并不是在声明基类时声明的,而是在声明派生类时,指定继承方式时声明的。因为一个基类可以在生成一个派生类时作为虚基类,而在生成另一个派生类时不作为虚基类。

声明虚基类的一般形式为:
   class 派生类名: virtual 继承方式  基类名
即在声明派生类时,将关键字 virtual 加到相应的继承方式前面,经过这样的声明后,当基类通过多条派生路径被一个派生类继承时,该派生类只继承该基类一次,也就是说,基类成员只保留一次。

需要注意:为了保证虚基类在派生类中只继承一次,应当在该基类的所有直接派生类中声明为虚基类。否则仍然会出现对基类的多次继承。

如果在派生类B和C中将类A声明为虚基类,而在派生类D中没有将类A声明为虚基类,则在派生类E中,虽然从类B和C路径派生的部分只保留一份基类成员,但从类D路径派生的部分还保留一份基类成员。

虚基类的初始化

如果在虚基类中定义了带参数的构造函数,而且没有定义默认构造函数,则在其所有派生类(包括直接派生或间接派生的派生类)中,通过构造函数的初始化表对虚基类进行初始化。例如

  1. class A //定义基类A
  2. {
  3. A(int i){ } //基类构造函数,有一个参数};
  4. class B :virtual public A //A作为B的虚基类
  5. {
  6. B(int n):A(n){ } //B类构造函数,在初始化表中对虚基类初始化
  7. };
  8. class C :virtual public A //A作为C的虚基类
  9. {
  10. C(int n):A(n){ } //C类构造函数,在初始化表中对虚基类初始化
  11. };
  12. class D :public B,public C //类D的构造函数,在初始化表中对所有基类初始化
  13. {
  14. D(int n):A(n),B(n),C(n){ }
  15. };

注意: 在定义类D的构造函数时,与以往使用的方法有所不同。以往,在派生类的构造函数中只需负责对其直接基类初始化,再由其直接基类负责对间接基类初始化。现在,由于虚基类在派生类中只有一份数据成员,所以这份数据成员的初始化必须由派生类直接给出。如果不由最后的派生类直接对虚基类初始化,而由虚基类的直接派生类(如类B和类C)对虚基类初始化,就有可能由于在类B和类C的构造函数中对虚基类给出不同的初始化参数而产生矛盾。所以规定:在最后的派生类中不仅要负责对其直接基类进行初始化,还要负责对虚基类初始化。

有的读者会提出:类D的构造函数通过初始化表调了虚基类的构造函数A,而类B和类C的构造函数也通过初始化表调用了虚基类的构造函数A,这样虚基类的构造函数岂非被调用了3次?大家不必过虑,C++编译系统只执行最后的派生类对虚基类的构造函数的调用,而忽略虚基类的其他派生类(如类B和类C) 对虚基类的构造函数的调用,这就保证了虚基类的数据成员不会被多次初始化。

虚基类的简单应用举例

[例11.9] 在例11. 8(具体代码请查看:C++类的多重继承)的基础上,在Teacher类和Student类之上增加一个共同的基类Person。作为人员的一些基本数据都放在Person中,在 Teacher类和Student类中再增加一些必要的数据。可写出以下程序:

  1. #include <iostream>
  2. #include <string>
  3. using namespace std;
  4. //声明公共基类Person
  5. class Person
  6. {
  7. public:
  8. Person(string nam,char s,int a) //构造函数
  9. {
  10. name=nam;
  11. sex=s;
  12. age=a;
  13. }
  14. protected: //保护成员
  15. string name;
  16. char sex;
  17. int age;
  18. };
  19. //声明Person的直接派生类Teacher
  20. class Teacher:virtual public Person //声明Person为公用继承的虚基类
  21. {
  22. public:
  23. Teacher(string nam,char s,int a, string t):Person(nam,s,a)//构造函数
  24. {
  25. title=t;
  26. }
  27. protected: //保护成员
  28. string title; //职称
  29. };
  30. //声明Person的直接派生类Student
  31. class Student:virtual public Person //声明Person为公用继承的虚基类
  32. {
  33. public:
  34. Student(string nam,char s,int a,float sco) //构造函数
  35. :Person(nam,s,a),score(sco){ } //初始化表
  36. protected: //保护成员
  37. float score; //成绩
  38. };
  39. //声明多重继承的派生类Graduate
  40. class Graduate:public Teacher,public Student //Teacher和Student为直接基类
  41. {
  42. public:
  43. Graduate(string nam,char s,int a, string t,float sco,float w)//构造函数
  44. :Person(nam,s,a),Teacher(nam,s,a,t),Student(nam,s,a,sco),wage(w){}
  45. //初始化表
  46. void show( ) //输出研究生的有关数据
  47. {
  48. cout<<"name:"<<name<<endl;
  49. cout<<"age:"<<age<<endl;
  50. cout<<"sex:"<<sex<<endl;
  51. cout<<"score:"<<score<<endl;
  52. cout<<"title:"<<title<<endl;
  53. cout<<"wages:"<<wage<<endl;
  54. }
  55. private:
  56. float wage; //工资
  57. };
  58. //主函数
  59. int main( )
  60. {
  61. Graduate grad1("Wang-li",‘f‘,24,"assistant",89.5,1234.5);
  62. grad1.show( );
  63. return 0;
  64. }

对程序的两点说明:
1) 请注意各类的构造函数的写法。在Person类中定义了包含3个形参的构造函数,用它对数据成员name、sex和age进行初始化。在Teacher和Student类的构造函数中,按规定要在初始化表中包含对基类的初始化,尽管对虚基类来说,编译系统不会由此调用基类的构造函数,但仍然应当按照派生类构造函数的统一格式书写。在最后派生类Graduate的构造函数中,既包括对虚基类构造函数的调用,也包括对其直接基类的初 始化。

2) 在Graduate类中,只保留一份基类的成员,因此可以用Graduate类中的show函数引用Graduate类对象中的公共基类Person的数据成员name、sex、age的值,不需要加基类名和域运算符(::),不会产生二义性。

可以看到:使用多重继承时要十分小心,经常会出现二义性问题。前面介绍的例子是简单的,如果派生的层次再多一些,多重继承更复杂一些,程序设计人员很容易陷人迷 魂阵,程序的编写、调试和维护工作都会变得更加困难。因此,许多专业人员认为:不要提倡在程序中使用多重继承,只有在比较简单和不易出现二义性的情况或实在必要时才使用多重继承,能用单一继承解决的问题就不要使用多重继承。也是由于这个原因,有些面向对象的程序设计语言(如Java,Smalltalk)并不支持多重继承。

文章出处:http://see.xidian.edu.cn/cpp/biancheng/view/238.html

时间: 2024-10-19 13:29:46

C++虚基类详解(转)的相关文章

C++虚基类详解

1.虚基类的作用从上面的介绍可知:如果一个派生类有多个直接基类,而这些直接基类又有一个共同的基类,则在最终的派生类中会保留该间接共同基类数据成员的多份同名成员.在引用这些同名的成员时,必须在派生类对象名后增加直接基类名,以避免产生二义性,使其惟一地标识一个成员,如    c1.A::display( ).在一个类中保留间接共同基类的多份同名成员,这种现象是人们不希望出现的.C++提供虚基类(virtual base class )的方法,使得在继承间接共同基类时只保留一份成员.现在,将类A声明为

C++学习20 虚基类详解

多继承时很容易产生命名冲突,即使我们很小心地将所有类中的成员变量和成员函数都命名为不同的名字,命名冲突依然有可能发生,比如非常经典的菱形继承层次.如下图所示: 类A派生出类B和类C,类D继承自类B和类C,这个时候类A中的成员变量和成员函数继承到类D中变成了两份,一份来自 A-->B-->D 这一路,另一份来自 A-->C-->D 这一条路. 在一个派生类中保留间接基类的多份同名成员,虽然可以在不同的成员变量中分别存放不同的数据,但大多数情况下这是多余的:因为保留多份成员变量不仅占用

VC++ 之 虚基类详解

在上一节中,有两个身份证号显然是不合理的.为此,可以把class Person这个共同基类设置为虚基类,这样,从不同路径继承来的同名数据成员在内存中就只有一个拷贝,同名函数也只有一种映射. 虚基类定义方式 虚基类(virtual base class)定义方式如下:    class 派生类名:virtual 访问限定符 基类类名{...};或:    class 派生类名:访问限定符 virtual 基类类名{...}; 其中:virtual 关键字只对紧随其后的基类名起作用. 例如:   

详解C++中基类与派生类的转换以及虚基类

很详细!转载链接 C++基类与派生类的转换在公用继承.私有继承和保护继承中,只有公用继承能较好地保留基类的特征,它保留了除构造函数和析构函数以外的基类所有成员,基类的公用或保护成员的访问权限在派生类中全部都按原样保留下来了,在派生类外可以调用基类的公用成员函数访问基类的私有成员.因此,公用派生类具有基类的全部功能,所有基类能够实现的功能, 公用派生类都能实现.而非公用派生类(私有或保护派生类)不能实现基类的全部功能(例如在派生类外不能调用基类的公用成员函数访问基类的私有成员).因此,只有公用派生

C++:虚函数的详解

5.4.2 虚函数详解 1.虚函数的定义 虚函数就是在基类中被关键字virtual说明,并在派生类重新定义的函数.虚函数的作用是允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数. 虚函数的定义是在基类中进行的,它是在基类中需要定义为虚函数的成员函数的声明中冠以关键字virtual.定义虚函数的格式如下: virtual 函数类型 函数名(形参表) {     函数体 } 在基类中的某个成员函数声明为虚函数后,此虚函数就可以在一个或多个派生类中被重新

C++多态篇3——虚函数表详解之多继承、虚函数表的打印

在上上一篇C++多态篇1一静态联编,动态联编.虚函数与虚函数表vtable中,我最后简单了剖析了一下虚函数表以及vptr. 而在上一篇文章C++多态篇2--虚函数表详解之从内存布局看函数重载,函数覆盖,函数隐藏中我详细介绍了虚函数的函数重载,函数覆盖以及函数隐藏的问题,其实在那一篇文章中,对单继承的虚函数已经做了十分详细的解答了,如果对前面有兴趣的人可以先看一下那篇文章. 在这一篇中,我会具体的分析一下在不同继承中(单继承,多继承)关于虚函数表在内存中的布局以及如何打印虚函数表.但是有关在虚继承

虚方法virtual详解

虚方法virtual详解 从C#的程序编译的角度来看,它和其它一般的函数有什么区别呢?一般函数在编译时就静态地编译到了执行文件中,其相对地址在程序运行期间是不发生变化的,也就是写死了的!而虚函数在编译期间是不被静态编译的,它的相对地址是不确定的,它会根据运行时期对象实例来动态判断要调用的函数,其中那个申明时定义的类叫申明类,那个执行时实例化的类叫实例类. 如:飞禽 bird = new 麻雀();那么飞禽就是申明类,麻雀是实例类. 具体的检查的流程如下 1.当调用一个对象的函数时,系统会直接去检

JavaScript学习总结(十一)——Object类详解

一.Object类介绍 Object类是所有JavaScript类的基类(父类),提供了一种创建自定义对象的简单方式,不再需要程序员定义构造函数. 二.Object类主要属性 1.constructor:对象的构造函数. 2.prototype:获得类的prototype对象,static性质. 三.Object类主要方法 1.hasOwnProperty(propertyName) 判断对象是否有某个特定的属性.必须用字符串指定该属性,例如,obj.hasOwnProperty("name&q

C++ Primer 学习笔记_35_面向对象编程(6)--虚函数与多态(三):虚函数表指针(vptr)及虚基类表指针(bptr)、C++对象模型

C++ Primer 学习笔记_35_面向对象编程(6)--虚函数与多态(三):虚函数表指针(vptr)及虚基类表指针(bptr).C++对象模型 一.虚函数表指针(vptr)及虚基类表指针(bptr) C++在布局以及存取时间上主要的额外负担是由virtual引起的,包括: virtual function机制:用以支持一个有效率的"执行期绑定": virtual base class:用以实现多次在继承体系中的基类,有一个单一而被共享的实体. 1.虚函数表指针 C++中,有两种数据