C++:虚基类
4.4.3 虚基类
1.为什么要引入虚基类
如果一个类有多个直接基类,而这些直接基类又有一个共同的基类,则在最底层的派生类中会保留这个间接的共同基类数据成员的多分同名成员。在访问这些同名的成员时,必须在派生类对象后增加直接基类名,使其惟一地标识一个成员,以免产生二义性。
//例 4.15 虚基类的引例
#include<iostream> using namespace std; class Base{ //声明类Base1和类Base2的共同的基类Base public: Base() { a = 5; cout<<"Base Construction."<<endl; cout<<"Base a = "<<a<<endl; } protected: int a; }; class Base1:public Base{ //声明类Base1是Base的派生类 public: int b1; Base1() { a = a+10; cout<<"Base1 Construction."<<endl; cout<<"Base1 a = "<<a<<endl; //这时类Base1的a,即Base1::a } }; class Base2:public Base{ //声明类Base2是Base的派生类 public: int b2; Base2() { a = a+20; cout<<"Base2 Construction."<<endl; cout<<"Base2 a = "<<a<<endl; //这时类Base2的a,即Base2::a } }; class Derived:public Base1,public Base2{ //Derived是Base1和Base2的共同派生类,是Base的间接派生类 public: int d; Derived() { cout<<"Derived Construction."<<endl; cout<<"Base1::a = "<<Base1::a<<endl; //在a前面加上"Base1::" cout<<"Base2::a = "<<Base2::a<<endl; //在a前面加上"Base2::" } }; int main() { Derived obj; return 0; } /* 运行结果如下: Base Construction. Base a = 5 Base1 Construction. Base1 a = 15 Base Construction. Base a = 5 Base2 Construction. Base2 a = 25 Derived Construction. Base1::a =15 Base2::a =25 发现:Base1和Base2派生出的类Derived继承了基类Base两次,也就是说,基类Base的成员a保留两份。 图4.3 例4.15的类层次图 图4.4 派生类Derived的成员情况 Base Base Derived |Base1 Base2| int Base1::a; |Derived| int Base1::b1; int Base2::a; int Base2::b2; int d; Derived(); */
2.虚基类的概念
不难理解,如果在上例类Base只存在一个复制(即只有一个数据成员),那么对a的引用就不会产生二义性。在C++中,如果想使这个公共的基类只产生一个复制,则可以将这个基类说明为虚基类。这就要求从类Base派生新类时,使用关键virtual将类Base说明为虚基类。
虚基类在派生类中的声明格式如下:
class 派生类名:virtual 继承方式 基类名{
......
};
经过这样声明后,当基类通过多条派生路径被一个派生类继承时,则该派生类只继承该基类一次,也就是说,基类成员只保留了一次。
//例4.16 虚基类的使用
#include<iostream> using namespace std; class Base{ //声明类Base1和类Base2的共同的基类Base public: Base() { a = 5; cout<<"Base Construction."<<endl; cout<<"Base a = "<<a<<endl; } protected: int a; }; class Base1:virtual public Base{ //声明类Base是Base1的虚基类 public: int b1; Base1() { a = a+10; cout<<"Base1 Construction."<<endl; cout<<"Base1 a = "<<a<<endl; } }; class Base2:virtual public Base{ //声明类Base是Base2的虚基类 public: int b2; Base2() { a = a+20; cout<<"Base2 Construction."<<endl; cout<<"Base2 a = "<<a<<endl; } }; class Derived:public Base1,public Base2{ //Derived是Base1和Base2的共同派生类,是Base的间接派生类 public: int d; Derived() { cout<<"Derived Construction."<<endl; cout<<"Derived a = "<<a<<endl; } }; int main() { Derived obj; return 0; } /* 运行结果如下: Base Construction. Base a = 5 Base1 Construction. Base1 a = 15 Base2 Construction. Base2 a = 35 Derived Construction. Derived a =35 发现:Base1和Base2派生出的类Derived只继承了基类Base一次,也就是说,基类Base的成员a只保留一份。 图4.3 例4.15的类层次图 图4.4 派生类Derived的成员情况 |Base| Derived (virtual)|Base1 Base2|(virtual) int a; |Derived| int Base1::b1; int Base2::b2; int d; Derived(); */
3.虚基类的初始化
虚基类的初始化与一般的多继承的初始化在语法上是一样的,但构造函数的调用顺序不同。
在使用虚基类机制时应该注意如下几点:
(1)如果在虚基类中定义有带有形参的构造函数,并且没有定义默认形式的构造函数,则整个继承机构中,所有直接或间接的派生类都必须在构造函数的成员初始化表中列出列出对虚基类构造函数的调用,以初始化在基类中定义的数据成员。
(2)建立一个对象时,如果这个对象中含有从虚基类继承来的成员,则虚基类的成员是由最远派生类的构造函数通过调用虚基类的构造函数来进行初始化的。该派生类的其他基类对虚基类构造函数的调用都自动被忽略。
(3)若同一层次中同时包含虚基类和非虚基类,应先调用虚基类的构造函数,再调用非虚基类的构造函数,最后调用派生类的构造函数。
(4)对于多个虚基类,构造函数的执行顺序仍然是先左后右,自上而下。
(5)对于非虚基类,构造函数的执行顺序仍然是先左后右,自上而下。
(6)若虚基类有非虚基类派生而来,则仍然先调用基类的构造函数,再调用派生类的构造函数。
例如(3)的举例:
class X:public Y,virtual public Z{
......
};
X obj;
定义类X的对象obj后,将产生如下的调用次序。
Z();
Y();
X();
//例4.17 虚基类的派生类构造函数的执行顺序
#include<iostream> using namespace std; class Base{ //声明虚基类Base public: Base(int sa) { a = sa; cout<<"Constructor Base"<<endl; cout<<"a="<<a<<endl; } private: int a; }; class Base1:virtual public Base{ //声明类Base是类Base1的虚基类 public: //在此必须缀上对类Base的构造函数的调用 Base1(int sa,int sb):Base(sa) { b = sb; cout<<"Constructor Base1"<<endl; cout<<"b="<<b<<endl; } private: int b; }; class Base2:virtual public Base{ //声明类Base是类Base2的虚基类 public: //在此必须缀上对类Base的构造函数的调用 Base2(int sa,int sc):Base(sa) { c = sc; cout<<"Constructor Base2"<<endl; cout<<"c="<<c<<endl; } private: int c; }; class Derived:public Base1,public Base2{ //Derived是类Base1和类Base2的共同派生类,是Base的间接派生类 public: Derived(int sa,int sb,int sc,int sd):Base(sa),Base1(sa,sb),Base2(sa,sc)//必须缀上对类Base的 { //构造函数的调用 d = sd; cout<<"Constructor Derived"<<endl; cout<<"d="<<d<<endl; } private: int d; }; int main() { Derived obj(2,4,6,8); return 0; }
发现:
在上述程序中,Base是一个虚基类,它只有一个带参数的构造函数,因此要求在派生类Base1、 Base2和Derived的构造函数的初始化表中,都必须带有对类Base的构造函数的调用。
注意:
如果Base不是虚基类,在派生类Derived的构造函数的初始化表中调用类Base的构造函数是错误的,但是当Base是虚基类且只有带参数的构造函数时,就必须在类derived的构造函数的初始化表中调用类Base的构造函数。因此,在类Derived构造函数的初始化表中,不仅含有对类Base1和Base2的构造函数的调用,还有对虚基类Base的构造函数的调用。
程序运行结果是:
Constructor Base
a=2
Constructor Base1
b=4
Constructor Base2
c=6
Constructor Derived
d=8
不难看出,上述程序中虚基类Base的构造函数只执行了一次。显然,当Derived的构造函数调用了虚基类Base的构造函数之后,类Base1和类Base2构造函数的调用被忽略了。这也就是初始化虚基类和初始化非虚基类不同的地方。
说明: (1)关键字virtual与派生类方式关键字(publi、private)的先后顺序无关紧要,它只是说明 是"虚拟派生"。例如以下两个虚拟派生的声明是等价的。 class Derived:virtual public Base{ ...... }; class Derived:public virtual Base{ ...... }; (2)一个基类在作为某些派生类虚基类的同时,又作为某些派生类非虚基类,这种情况是允许存在的, 例如: class B{ ...... }; class X:virtual public B{ ...... }; class Y:virtual public B{ ...... }; class Z:public B{ ...... }; class AA:public X,public Y,public Z{ ...... };
4.虚基类的简单应用举例
例4.18 类Data_rec是虚基类,它包含了所有派生类共有的数据成员,职工类Employee和学生类Student为虚基类Data_rec的派生类,在职大学生类E_Student是职工类Employee和学生类Student的共同派生类。
#include<iostream> #include<string> using namespace std; class Data_rec{ //声明虚基类Data_rec public: Data_rec(string name1,char sex1,int age1) //虚基类的构造函数 { name = name1; sex = sex1; age = age1; } protected: string name; char sex; int age; }; class Student:virtual public Data_rec{ //声明虚基类Data_rec的派生类Student public: //声明虚基类Data_rec的派生类Student的构造函数,并缀上虚基类构造函数的调用 Student(string name1,char sex1,int age1,string major1,double score1):Data_rec(name1,sex1,age1) { major = major1; score = score1; } protected: string major; double score; }; class Employee:public virtual Data_rec{ //声明虚基类Data_rec的派生类Employee public: //声明虚基类Data_rec的派生类Employee的构造函数,并缀上虚基类构造函数的调用 Employee(string name1,char sex1,int age1,string dept1,int salary1):Data_rec(name1,sex1,age1) { dept = dept1; salary = salary1; } protected: string dept; double salary; }; class E_Student:public Student,public Employee{//在职大学生类E_Student是职工类Employee和学生类Student的共同派生类 //在职大学生类E_Student的构造函数,并缀上基类Data_rec和职工类Employee、学生类Student的构造函数的调用 public: E_Student(string name1,char sex1,int age1,string major1,int score1,string dept1,double salary1): Data_rec(name1,sex1,age1),Student(name1,sex1,age1,major1,score1),Employee(name1,sex1,age1,dept1,salary1) {} void print(); }; void E_Student::print() { cout<<"name:"<<name<<endl; cout<<"sex:"<<sex<<endl; cout<<"age:"<<age<<endl; cout<<"major:"<<major<<endl; cout<<"score:"<<score<<endl; cout<<"dept:"<<dept<<endl; cout<<"salary:"<<salary<<endl; } int main() { E_Student obj("张大明",'男',35,"计算机",95,"教务处",3500); obj.print(); return 0; }