C++虚基类详解
我們知道,如果一個(gè)派生類(lèi)有多個(gè)直接基類(lèi),而這些直接基類(lèi)又有一個(gè)共同的基類(lèi),則在最終的派生類(lèi)中會(huì)保留該間接共同基類(lèi)數(shù)據(jù)成員的多份同名成員。在引用這些同名的成員時(shí),必須在派生類(lèi)對(duì)象名后增加直接基類(lèi)名,以避免產(chǎn)生二義性,使其惟一地標(biāo)識(shí)一個(gè)成員,如:
? ? c1.A::display( )
在一個(gè)類(lèi)中保留間接共同基類(lèi)的多份同名成員,雖然有時(shí)是有必要的,可以在不同的數(shù)據(jù)成員中分別存放不同的數(shù)據(jù),也可以通過(guò)構(gòu)造函數(shù)分別對(duì)它們進(jìn)行初始化。但在大多數(shù)情況下,這種現(xiàn)象是人們不希望出現(xiàn)的。因?yàn)楸A舳喾輸?shù)據(jù)成員的拷貝,不僅占用較多的存儲(chǔ)空間,還增加了訪(fǎng)問(wèn)這些成員時(shí)的困難,容易出錯(cuò)。而且在實(shí)際上,并不需要有多份拷貝。
C++提供虛基類(lèi)(virtual base class)的方法,使得在繼承間接共同基類(lèi)時(shí)只保留一份成員。假設(shè)類(lèi)D是類(lèi)B和類(lèi)C公用派生類(lèi),而類(lèi)B和類(lèi)C又是類(lèi)A的派生類(lèi),如圖11.21所示。 設(shè)類(lèi)A有數(shù)據(jù)成員data和成員函數(shù)fun;派生類(lèi)B和C分別從類(lèi)A繼承了data和fun,此外類(lèi)B還增加了自己的數(shù)據(jù)成員data_b,類(lèi)C增加了數(shù)據(jù)成員data_c。如果不用虛基類(lèi),就會(huì)在類(lèi)D中保留了類(lèi)A成員data的兩份拷貝,分別表示為int B::data和int C::data。同樣有兩個(gè)同名的成員函數(shù),表示為void B::fun()和void C::fun()。類(lèi)B中增加的成員data_b和類(lèi)C中增加的成員dat_c不同名,不必用類(lèi)名限定。此外,類(lèi)D還增加了自己的數(shù)據(jù)成員data_d和成員函數(shù)fun_d。
圖 11.21
現(xiàn)在,將類(lèi)A聲明為虛基類(lèi),方法如下: 復(fù)制純文本新窗口class A //聲明基類(lèi)A { // 代碼 }; class B: virtual public A //聲明類(lèi)B是類(lèi)A的公用派生類(lèi),A是B的虛基類(lèi) { // 代碼 }; class C: virtual public A //聲明類(lèi)C是類(lèi)A的公用派生類(lèi),A是C的虛基類(lèi) { // 代碼 }; class A //聲明基類(lèi)A
{// 代碼
};
class B: virtual public A //聲明類(lèi)B是類(lèi)A的公用派生類(lèi),A是B的虛基類(lèi)
{// 代碼
};
class C: virtual public A //聲明類(lèi)C是類(lèi)A的公用派生類(lèi),A是C的虛基類(lèi)
{// 代碼
}; 注意: 虛基類(lèi)并不是在聲明基類(lèi)時(shí)聲明的,而是在聲明派生類(lèi)時(shí),指定繼承方式時(shí)聲明的。因?yàn)橐粋€(gè)基類(lèi)可以在生成一個(gè)派生類(lèi)時(shí)作為虛基類(lèi),而在生成另一個(gè)派生類(lèi)時(shí)不作為虛基類(lèi)。
聲明虛基類(lèi)的一般形式為:
? ?class 派生類(lèi)名: virtual 繼承方式 ?基類(lèi)名
即在聲明派生類(lèi)時(shí),將關(guān)鍵字 virtual 加到相應(yīng)的繼承方式前面,經(jīng)過(guò)這樣的聲明后,當(dāng)基類(lèi)通過(guò)多條派生路徑被一個(gè)派生類(lèi)繼承時(shí),該派生類(lèi)只繼承該基類(lèi)一次,也就是說(shuō),基類(lèi)成員只保留一次。
需要注意:為了保證虛基類(lèi)在派生類(lèi)中只繼承一次,應(yīng)當(dāng)在該基類(lèi)的所有直接派生類(lèi)中聲明為虛基類(lèi)。否則仍然會(huì)出現(xiàn)對(duì)基類(lèi)的多次繼承。
如果在派生類(lèi)B和C中將類(lèi)A聲明為虛基類(lèi),而在派生類(lèi)D中沒(méi)有將類(lèi)A聲明為虛基類(lèi),則在派生類(lèi)E中,雖然從類(lèi)B和C路徑派生的部分只保留一份基類(lèi)成員,但從類(lèi)D路徑派生的部分還保留一份基類(lèi)成員。class A //定義基類(lèi)A { A(int i){ } //基類(lèi)構(gòu)造函數(shù),有一個(gè)參數(shù)}; class B :virtual public A //A作為B的虛基類(lèi) { B(int n):A(n){ } //B類(lèi)構(gòu)造函數(shù),在初始化表中對(duì)虛基類(lèi)初始化 }; class C :virtual public A //A作為C的虛基類(lèi) { C(int n):A(n){ } //C類(lèi)構(gòu)造函數(shù),在初始化表中對(duì)虛基類(lèi)初始化 }; class D :public B,public C //類(lèi)D的構(gòu)造函數(shù),在初始化表中對(duì)所有基類(lèi)初始化 { D(int n):A(n),B(n),C(n){ } }; class A //定義基類(lèi)A
{A(int i){ } //基類(lèi)構(gòu)造函數(shù),有一個(gè)參數(shù)};
class B :virtual public A //A作為B的虛基類(lèi)
{B(int n):A(n){ } //B類(lèi)構(gòu)造函數(shù),在初始化表中對(duì)虛基類(lèi)初始化
};
class C :virtual public A //A作為C的虛基類(lèi)
{C(int n):A(n){ } //C類(lèi)構(gòu)造函數(shù),在初始化表中對(duì)虛基類(lèi)初始化
};
class D :public B,public C //類(lèi)D的構(gòu)造函數(shù),在初始化表中對(duì)所有基類(lèi)初始化
{D(int n):A(n),B(n),C(n){ }
}; 注意: 在定義類(lèi)D的構(gòu)造函數(shù)時(shí),與以往使用的方法有所不同。以往,在派生類(lèi)的構(gòu)造函數(shù)中只需負(fù)責(zé)對(duì)其直接基類(lèi)初始化,再由其直接基類(lèi)負(fù)責(zé)對(duì)間接基類(lèi)初始化。現(xiàn)在,由于虛基類(lèi)在派生類(lèi)中只有一份數(shù)據(jù)成員,所以這份數(shù)據(jù)成員的初始化必須由派生類(lèi)直接給出。如果不由最后的派生類(lèi)直接對(duì)虛基類(lèi)初始化,而由虛基類(lèi)的直接派生類(lèi)(如類(lèi)B和類(lèi)C)對(duì)虛基類(lèi)初始化,就有可能由于在類(lèi)B和類(lèi)C的構(gòu)造函數(shù)中對(duì)虛基類(lèi)給出不同的初始化參數(shù)而產(chǎn)生矛盾。所以規(guī)定:在最后的派生類(lèi)中不僅要負(fù)責(zé)對(duì)其直接基類(lèi)進(jìn)行初始化,還要負(fù)責(zé)對(duì)虛基類(lèi)初始化。
有的讀者會(huì)提出:類(lèi)D的構(gòu)造函數(shù)通過(guò)初始化表調(diào)了虛基類(lèi)的構(gòu)造函數(shù)A,而類(lèi)B和類(lèi)C的構(gòu)造函數(shù)也通過(guò)初始化表調(diào)用了虛基類(lèi)的構(gòu)造函數(shù)A,這樣虛基類(lèi)的構(gòu)造函數(shù)豈非被調(diào)用了3次?大家不必過(guò)慮,C++編譯系統(tǒng)只執(zhí)行最后的派生類(lèi)對(duì)虛基類(lèi)的構(gòu)造函數(shù)的調(diào)用,而忽略虛基類(lèi)的其他派生類(lèi)(如類(lèi)B和類(lèi)C) 對(duì)虛基類(lèi)的構(gòu)造函數(shù)的調(diào)用,這就保證了虛基類(lèi)的數(shù)據(jù)成員不會(huì)被多次初始化。#include <iostream> #include <string> using namespace std; //聲明公共基類(lèi)Person class Person { public: Person(string nam,char s,int a) //構(gòu)造函數(shù) { name=nam; sex=s; age=a; } protected: //保護(hù)成員 string name; char sex; int age; }; ? //聲明Person的直接派生類(lèi)Teacher class Teacher:virtual public Person //聲明Person為公用繼承的虛基類(lèi) { public: Teacher(string nam,char s,int a, string t):Person(nam,s,a)//構(gòu)造函數(shù) { title=t; } protected: //保護(hù)成員 string title; //職稱(chēng) }; ? //聲明Person的直接派生類(lèi)Student class Student:virtual public Person //聲明Person為公用繼承的虛基類(lèi) { public: Student(string nam,char s,int a,float sco) //構(gòu)造函數(shù) :Person(nam,s,a),score(sco){ } //初始化表 protected: //保護(hù)成員 float score; //成績(jī) }; ? //聲明多重繼承的派生類(lèi)Graduate class Graduate:public Teacher,public Student //Teacher和Student為直接基類(lèi) { public: Graduate(string nam,char s,int a, string t,float sco,float w)//構(gòu)造函數(shù) :Person(nam,s,a),Teacher(nam,s,a,t),Student(nam,s,a,sco),wage(w){} //初始化表 void show( ) //輸出研究生的有關(guān)數(shù)據(jù) { cout<<"name:"<<name<<endl; cout<<"age:"<<age<<endl; cout<<"sex:"<<sex<<endl; cout<<"score:"<<score<<endl; cout<<"title:"<<title<<endl; cout<<"wages:"<<wage<<endl; } private: float wage; //工資 }; ? //主函數(shù) int main( ) { Graduate grad1("Wang-li",'f',24,"assistant",89.5,1234.5); grad1.show( ); return 0; } #include <iostream>
#include <string>
using namespace std;
//聲明公共基類(lèi)Person
class Person
{
public:Person(string nam,char s,int a) //構(gòu)造函數(shù){name=nam;sex=s;age=a;}
protected: //保護(hù)成員string name;char sex;int age;
};//聲明Person的直接派生類(lèi)Teacher
class Teacher:virtual public Person //聲明Person為公用繼承的虛基類(lèi)
{
public: Teacher(string nam,char s,int a, string t):Person(nam,s,a)//構(gòu)造函數(shù){title=t;}
protected: //保護(hù)成員string title; //職稱(chēng)
};//聲明Person的直接派生類(lèi)Student
class Student:virtual public Person //聲明Person為公用繼承的虛基類(lèi)
{
public:Student(string nam,char s,int a,float sco) //構(gòu)造函數(shù):Person(nam,s,a),score(sco){ } //初始化表
protected: //保護(hù)成員float score; //成績(jī)
};//聲明多重繼承的派生類(lèi)Graduate
class Graduate:public Teacher,public Student //Teacher和Student為直接基類(lèi)
{
public:Graduate(string nam,char s,int a, string t,float sco,float w)//構(gòu)造函數(shù):Person(nam,s,a),Teacher(nam,s,a,t),Student(nam,s,a,sco),wage(w){}//初始化表void show( ) //輸出研究生的有關(guān)數(shù)據(jù){cout<<"name:"<<name<<endl;cout<<"age:"<<age<<endl;cout<<"sex:"<<sex<<endl;cout<<"score:"<<score<<endl;cout<<"title:"<<title<<endl;cout<<"wages:"<<wage<<endl;}
private:float wage; //工資
};//主函數(shù)
int main( )
{Graduate grad1("Wang-li",'f',24,"assistant",89.5,1234.5);grad1.show( );return 0;
}
對(duì)程序的兩點(diǎn)說(shuō)明:
1) 請(qǐng)注意各類(lèi)的構(gòu)造函數(shù)的寫(xiě)法。在Person類(lèi)中定義了包含3個(gè)形參的構(gòu)造函數(shù),用它對(duì)數(shù)據(jù)成員name、sex和age進(jìn)行初始化。在Teacher和Student類(lèi)的構(gòu)造函數(shù)中,按規(guī)定要在初始化表中包含對(duì)基類(lèi)的初始化,盡管對(duì)虛基類(lèi)來(lái)說(shuō),編譯系統(tǒng)不會(huì)由此調(diào)用基類(lèi)的構(gòu)造函數(shù),但仍然應(yīng)當(dāng)按照派生類(lèi)構(gòu)造函數(shù)的統(tǒng)一格式書(shū)寫(xiě)。在最后派生類(lèi)Graduate的構(gòu)造函數(shù)中,既包括對(duì)虛基類(lèi)構(gòu)造函數(shù)的調(diào)用,也包括對(duì)其直接基類(lèi)的初 始化。
2) 在Graduate類(lèi)中,只保留一份基類(lèi)的成員,因此可以用Graduate類(lèi)中的show函數(shù)引用Graduate類(lèi)對(duì)象中的公共基類(lèi)Person的數(shù)據(jù)成員name、sex、age的值,不需要加基類(lèi)名和域運(yùn)算符(::),不會(huì)產(chǎn)生二義性。
可以看到:使用多重繼承時(shí)要十分小心,經(jīng)常會(huì)出現(xiàn)二義性問(wèn)題。前面介紹的例子是簡(jiǎn)單的,如果派生的層次再多一些,多重繼承更復(fù)雜一些,程序設(shè)計(jì)人員很容易陷人迷 魂陣,程序的編寫(xiě)、調(diào)試和維護(hù)工作都會(huì)變得更加困難。因此,許多專(zhuān)業(yè)人員認(rèn)為:不要提倡在程序中使用多重繼承,只有在比較簡(jiǎn)單和不易出現(xiàn)二義性的情況或?qū)嵲诒匾獣r(shí)才使用多重繼承,能用單一繼承解決的問(wèn)題就不要使用多重繼承。也是由于這個(gè)原因,有些面向?qū)ο蟮某绦蛟O(shè)計(jì)語(yǔ)言(如Java,Smalltalk)并不支持多重繼承。
? ? c1.A::display( )
在一個(gè)類(lèi)中保留間接共同基類(lèi)的多份同名成員,雖然有時(shí)是有必要的,可以在不同的數(shù)據(jù)成員中分別存放不同的數(shù)據(jù),也可以通過(guò)構(gòu)造函數(shù)分別對(duì)它們進(jìn)行初始化。但在大多數(shù)情況下,這種現(xiàn)象是人們不希望出現(xiàn)的。因?yàn)楸A舳喾輸?shù)據(jù)成員的拷貝,不僅占用較多的存儲(chǔ)空間,還增加了訪(fǎng)問(wèn)這些成員時(shí)的困難,容易出錯(cuò)。而且在實(shí)際上,并不需要有多份拷貝。
C++提供虛基類(lèi)(virtual base class)的方法,使得在繼承間接共同基類(lèi)時(shí)只保留一份成員。假設(shè)類(lèi)D是類(lèi)B和類(lèi)C公用派生類(lèi),而類(lèi)B和類(lèi)C又是類(lèi)A的派生類(lèi),如圖11.21所示。 設(shè)類(lèi)A有數(shù)據(jù)成員data和成員函數(shù)fun;派生類(lèi)B和C分別從類(lèi)A繼承了data和fun,此外類(lèi)B還增加了自己的數(shù)據(jù)成員data_b,類(lèi)C增加了數(shù)據(jù)成員data_c。如果不用虛基類(lèi),就會(huì)在類(lèi)D中保留了類(lèi)A成員data的兩份拷貝,分別表示為int B::data和int C::data。同樣有兩個(gè)同名的成員函數(shù),表示為void B::fun()和void C::fun()。類(lèi)B中增加的成員data_b和類(lèi)C中增加的成員dat_c不同名,不必用類(lèi)名限定。此外,類(lèi)D還增加了自己的數(shù)據(jù)成員data_d和成員函數(shù)fun_d。
圖 11.21
現(xiàn)在,將類(lèi)A聲明為虛基類(lèi),方法如下: 復(fù)制純文本新窗口
聲明虛基類(lèi)的一般形式為:
? ?class 派生類(lèi)名: virtual 繼承方式 ?基類(lèi)名
即在聲明派生類(lèi)時(shí),將關(guān)鍵字 virtual 加到相應(yīng)的繼承方式前面,經(jīng)過(guò)這樣的聲明后,當(dāng)基類(lèi)通過(guò)多條派生路徑被一個(gè)派生類(lèi)繼承時(shí),該派生類(lèi)只繼承該基類(lèi)一次,也就是說(shuō),基類(lèi)成員只保留一次。
需要注意:為了保證虛基類(lèi)在派生類(lèi)中只繼承一次,應(yīng)當(dāng)在該基類(lèi)的所有直接派生類(lèi)中聲明為虛基類(lèi)。否則仍然會(huì)出現(xiàn)對(duì)基類(lèi)的多次繼承。
如果在派生類(lèi)B和C中將類(lèi)A聲明為虛基類(lèi),而在派生類(lèi)D中沒(méi)有將類(lèi)A聲明為虛基類(lèi),則在派生類(lèi)E中,雖然從類(lèi)B和C路徑派生的部分只保留一份基類(lèi)成員,但從類(lèi)D路徑派生的部分還保留一份基類(lèi)成員。
虛基類(lèi)的初始化
如果在虛基類(lèi)中定義了帶參數(shù)的構(gòu)造函數(shù),而且沒(méi)有定義默認(rèn)構(gòu)造函數(shù),則在其所有派生類(lèi)(包括直接派生或間接派生的派生類(lèi))中,通過(guò)構(gòu)造函數(shù)的初始化表對(duì)虛基類(lèi)進(jìn)行初始化。例如 復(fù)制純文本新窗口有的讀者會(huì)提出:類(lèi)D的構(gòu)造函數(shù)通過(guò)初始化表調(diào)了虛基類(lèi)的構(gòu)造函數(shù)A,而類(lèi)B和類(lèi)C的構(gòu)造函數(shù)也通過(guò)初始化表調(diào)用了虛基類(lèi)的構(gòu)造函數(shù)A,這樣虛基類(lèi)的構(gòu)造函數(shù)豈非被調(diào)用了3次?大家不必過(guò)慮,C++編譯系統(tǒng)只執(zhí)行最后的派生類(lèi)對(duì)虛基類(lèi)的構(gòu)造函數(shù)的調(diào)用,而忽略虛基類(lèi)的其他派生類(lèi)(如類(lèi)B和類(lèi)C) 對(duì)虛基類(lèi)的構(gòu)造函數(shù)的調(diào)用,這就保證了虛基類(lèi)的數(shù)據(jù)成員不會(huì)被多次初始化。
虛基類(lèi)的簡(jiǎn)單應(yīng)用舉例
[例11.9] 在例11. 8(具體代碼請(qǐng)查看:C++類(lèi)的多重繼承)的基礎(chǔ)上,在Teacher類(lèi)和Student類(lèi)之上增加一個(gè)共同的基類(lèi)Person。作為人員的一些基本數(shù)據(jù)都放在Person中,在 Teacher類(lèi)和Student類(lèi)中再增加一些必要的數(shù)據(jù)。可寫(xiě)出以下程序: 復(fù)制純文本新窗口對(duì)程序的兩點(diǎn)說(shuō)明:
1) 請(qǐng)注意各類(lèi)的構(gòu)造函數(shù)的寫(xiě)法。在Person類(lèi)中定義了包含3個(gè)形參的構(gòu)造函數(shù),用它對(duì)數(shù)據(jù)成員name、sex和age進(jìn)行初始化。在Teacher和Student類(lèi)的構(gòu)造函數(shù)中,按規(guī)定要在初始化表中包含對(duì)基類(lèi)的初始化,盡管對(duì)虛基類(lèi)來(lái)說(shuō),編譯系統(tǒng)不會(huì)由此調(diào)用基類(lèi)的構(gòu)造函數(shù),但仍然應(yīng)當(dāng)按照派生類(lèi)構(gòu)造函數(shù)的統(tǒng)一格式書(shū)寫(xiě)。在最后派生類(lèi)Graduate的構(gòu)造函數(shù)中,既包括對(duì)虛基類(lèi)構(gòu)造函數(shù)的調(diào)用,也包括對(duì)其直接基類(lèi)的初 始化。
2) 在Graduate類(lèi)中,只保留一份基類(lèi)的成員,因此可以用Graduate類(lèi)中的show函數(shù)引用Graduate類(lèi)對(duì)象中的公共基類(lèi)Person的數(shù)據(jù)成員name、sex、age的值,不需要加基類(lèi)名和域運(yùn)算符(::),不會(huì)產(chǎn)生二義性。
可以看到:使用多重繼承時(shí)要十分小心,經(jīng)常會(huì)出現(xiàn)二義性問(wèn)題。前面介紹的例子是簡(jiǎn)單的,如果派生的層次再多一些,多重繼承更復(fù)雜一些,程序設(shè)計(jì)人員很容易陷人迷 魂陣,程序的編寫(xiě)、調(diào)試和維護(hù)工作都會(huì)變得更加困難。因此,許多專(zhuān)業(yè)人員認(rèn)為:不要提倡在程序中使用多重繼承,只有在比較簡(jiǎn)單和不易出現(xiàn)二義性的情況或?qū)嵲诒匾獣r(shí)才使用多重繼承,能用單一繼承解決的問(wèn)題就不要使用多重繼承。也是由于這個(gè)原因,有些面向?qū)ο蟮某绦蛟O(shè)計(jì)語(yǔ)言(如Java,Smalltalk)并不支持多重繼承。
總結(jié)
- 上一篇: 虚函数的实现机制
- 下一篇: Google C++编程风格指南