虛基類的函數編譯嗎
① 請教關於C++中虛基類的一個問題
這是個菱形繼承,菱形繼承
即是
子類有兩個父類,這兩個父類都繼承自同一個類。這時候,如果不使用
虛基類
,
定義
子類的時候,會生成兩個「祖父」基類的樣本,在調用「祖父」基類的成員函數的時候,就會出現「歧義」錯誤,無法編譯通過。解決的辦法就是使用虛基類,這樣就只保留一個「祖父」基類的樣本,調用也不會出現「歧義」了。
如上面例子,不使用虛基類,在「祖父」基類Base中定義一個show1
函數
,在類C中調用就會出現問題:
#include
<iostream>
using
namespace
std
;
class
Base{
public:
void
show(){cout<<"Base
class"<<endl;}
void
show1(){cout<<"Base
class
1"<<endl;}
};
class
A:
public
Base{
public:
void
show(){cout<<"Class
A"<<endl;}
};
class
B:
public
Base{
public:
void
show(){cout<<"Class
B"<<endl;}
};
class
C:
public
A,public
B{
public:
void
show(){cout<<"Class
C"<<endl;}
};
int
main(){
C
c;
c.show();
c.show1();
return
0;
}
報錯信息如下:
error
C2385:
'C::show1'
is
ambiguous
warning
C4385:
could
be
the
'show1'
in
base
'Base'
of
base
'A'
of
class
'C'
warning
C4385:
or
the
'show1'
in
base
'Base'
of
base
'B'
of
class
'C'
改成虛基類就
沒問題
了。
② 虛基類和非虛基類在編寫方法上有什麼不同,是不是只是多了一個 virtual
您好,C++編譯器們必須實現語言的每一個特性。這些實現的細節當然是由編譯器來決定的,並且不同的編譯器有不同的方法實現語言的特性。在多數情況下,你不用關心這些事情。然而有些特性的實現對對象大小和其成員函數執行速度有很大的影響,所以對於這些特性有一個基本的了解,知道編譯器可能在背後做了些什麼,就顯得很重要。這種特性中最重要的例子是虛擬函數。
當調用一個虛擬函數時,被執行的代碼必須與調用函數的對象的動態類型相一致;指向對象的指針或引用的類型是不重要的。編譯器如何能夠高效地提供這種行為呢?大多數編譯器是使用virtual table和virtual table pointers。virtual table和virtual table pointers通常被分別地稱為vtbl和vptr。
一個vtbl通常是一個函數指針數組。(一些編譯器使用鏈表來代替數組,但是基本方法是一樣的)在程序中的每個類只要聲明了虛函數或繼承了虛函數,它就有自己的vtbl,並且類中vtbl的項目是指向虛函數實現體的指針。例如,如下這個類定義:
class C1 {
public:
C1();
virtual ~C1();
virtual void f1();
virtual int f2(char c) const;
virtual void f3(const string& s);
void f4() const;
...
};
C1的virtual table數組看起來如下圖所示:
注意非虛函數f4不在表中,而且C1的構造函數也不在。非虛函數(包括構造函數,它也被定義為非虛函數)就象普通的C函數那樣被實現,所以有關它們的使用在性能上沒有特殊的考慮。
如果有一個C2類繼承自C1,重新定義了它繼承的一些虛函數,並加入了它自己的一些虛函數,
class C2: public C1 {
public:
C2(); // 非虛函數
virtual ~C2(); // 重定義函數
virtual void f1(); // 重定義函數
virtual void f5(char *str); // 新的虛函數
...
};
它的virtual table項目指向與對象相適合的函數。這些項目包括指向沒有被C2重定義的C1虛函數的指針:
這個論述引出了虛函數所需的第一個代價:你必須為每個包含虛函數的類的virtual talbe留出空間。類的vtbl的大小與類中聲明的虛函數的數量成正比(包括從基類繼承的虛函數)。每個類應該只有一個virtual table,所以virtual table所需的空間不會太大,但是如果你有大量的類或者在每個類中有大量的虛函數,你會發現vtbl會佔用大量的地址空間。
因為在程序里每個類只需要一個vtbl拷貝,所以編譯器肯定會遇到一個棘手的問題:把它放在哪裡。大多數程序和程序庫由多個object(目標)文件連接而成,但是每個object文件之間是獨立的。哪個object文件應該包含給定類的vtbl呢?你可能會認為放在包含main函數的object文件里,但是程序庫沒有main,而且無論如何包含main的源文件不會涉及很多需要vtbl的類。編譯器如何知道它們被要求建立那一個vtbl呢?
必須採取一種不同的方法,編譯器廠商為此分成兩個陣營。對於提供集成開發環境(包含編譯程序和連接程序)的廠商,一種乾脆的方法是為每一個可能需要vtbl的object文件生成一個vtbl拷貝。連接程序然後去除重復的拷貝,在最後的可執行文件或程序庫里就為每個vtbl保留一個實例。
更普通的設計方法是採用啟發式演算法來決定哪一個object文件應該包含類的vtbl。通常啟發式演算法是這樣的:要在一個object文件中生成一個類的vtbl,要求該object文件包含該類的第一個非內聯、非純虛擬函數(non-inline non-pure virual function)定義(也就是類的實現體)。因此上述C1類的vtbl將被放置到包含C1::~C1定義的object文件里(不是內聯的函數),C2類的vtbl被放置到包含C1::~C2定義的object文件里(不是內聯函數)。
實際當中,這種啟發式演算法效果很好。但是如果你過分喜歡聲明虛函數為內聯函數(參見Effective C++條款33),如果在類中的所有虛函數都內聲明為內聯函數,啟發式演算法就會失敗,大多數基於啟發式演算法的編譯器會在每個使用它的object文件中生成一個類的vtbl。在大型系統里,這會導致程序包含同一個類的成百上千個vtbl拷貝!大多數遵循這種啟發式演算法的編譯器會給你一些方法來人工控制vtbl的生成,但是一種更好的解決此問題的方法是避免把虛函數聲明為內聯函數。下面我們將看到,有一些原因導致現在的編譯器一般總是忽略虛函數的的inline指令。
③ 虛基類的一個問題
要訪問的變數或函數聲明在派生類上,應改為派生類的指針(派生類 *)
這是因為,基類並不知道派生類增加了什麼內容。實際上就是,編譯器不能根據基類指針來判斷派生類中新增變數的位置和新增函數的入口點。若訪問的函數本來就在基類中聲明過的(包括虛函數和純虛函數),就不必要修改。
要訪問派生類重寫的函數(函數名相同,但不是虛函數),應改為派生類的指針。
大概這些吧。
④ 虛繼承的虛繼承與虛基類的本質
虛繼承和虛基類的定義是非常的簡單的,同時也是非常容易判斷一個繼承是否是虛繼承的,雖然這兩個概念的定義是非常的簡單明確的,但是在C++語言中虛繼承作為一個比較生僻的但是又是絕對必要的組成部份而存在著,並且其行為和模型均表現出和一般的繼承體系之間的巨大的差異(包括訪問性能上的差異),現在我們就來徹底的從語言、模型、性能和應用等多個方面對虛繼承和虛基類進行研究。
首先還是先給出虛繼承和虛基類的定義。
虛繼承:在繼承定義中包含了virtual關鍵字的繼承關系;
虛基類:在虛繼承體系中的通過virtual繼承而來的基類,需要注意的是:
struct CSubClass : public virtual CBase {}; 其中CBase稱之為CSubClass
的虛基類,而不是說CBase就是個虛基類,因為CBase還可以作為不是虛繼承體系中的基類。
有了上面的定義後,就可以開始虛繼承和虛基類的本質研究了,下面按照語法、語義、模型、性能和應用五個方面進行全面的描述。 語法有語言的本身的定義所決定,總體上來說非常的簡單,如下:
struct CSubClass : public virtual CBaseClass {};
其中可以採用public、protected、private三種不同的繼承關鍵字進行修飾,只要確保包含virtual就可以了,這樣一來就形成了虛繼承體系,同時CBaseClass就成為了CSubClass的虛基類了。
其實並沒有那麼的簡單,如果出現虛繼承體系的進一步繼承會出現什麼樣的狀況呢?
如下所示:
/*
* 帶有數據成員的基類
*/
struct CBaseClass1
{
CBaseClass1( size_t i ) : m_val( i ) {}
size_t m_val;
};
/*
* 虛擬繼承體系
*/
struct CSubClassV1 : public virtual CBaseClass1
{
CSubClassV1( size_t i ) : CBaseClass1( i ) {}
};
struct CSubClassV2 : public virtual CBaseClass1
{
CSubClassV2( size_t i ) : CBaseClass1( i ) {}
};
struct CDiamondClass1 : public CSubClassV1, public CSubClassV2
{
CDiamondClass1( size_t i ) : CBaseClass1( i ), CSubClassV1( i ), CSubClassV2( i ) {}
};
struct CDiamondSubClass1 : public CDiamondClass1
{
CDiamondSubClass1( size_t i ) : CBaseClass1( i ), CDiamondClass1( i ) {}
};
注意上面代碼中的CDiamondClass1和CDiamondSubClass1兩個類的構造函數初始化列表中的內容。可以發現其中均包含了虛基類CBaseClass1的初始化工作,如果沒有這個初始化語句就會導致編譯時錯誤,為什麼會這樣呢?一般情況下不是只要在CSubClassV1和CSubClassV2中包含初始化就可以了么?要解釋該問題必須要明白虛繼承的語義特徵,所以參看下面語義部分的解釋。 從語義上來講什麼是虛繼承和虛基類呢?上面僅僅是從如何在C++語言中書寫合法的虛繼承類定義而已。首先來了解一下virtual這個關鍵字在C++中的公共含義,在C++語言中僅僅有兩個地方可以使用virtual這個關鍵字,一個就是類成員虛函數和這里所討論的虛繼承。不要看這兩種應用場合好像沒什麼關系,其實他們在背景語義上具有virtual這個詞所代表的共同的含義,所以才會在這兩種場合使用相同的關鍵字。
那麼virtual這個詞的含義是什麼呢?
virtual在《美國傳統詞典[雙解]》中是這樣定義的:
adj.(形容詞)
1. Existing or resulting in essence or effect though not in actual fact, form, or name:
實質上的,實際上的:雖然沒有實際的事實、形式或名義,但在實際上或效果上存在或產生的;
2. Existing in the mind, especially as a proct of the imagination. Used in literary criticism of text.
虛的,內心的:在頭腦中存在的,尤指意想的產物。用於文學批評中。
我們採用第一個定義,也就是說被virtual所修飾的事物或現象在本質上是存在的,但是沒有直觀的形式表現,無法直接描述或定義,需要通過其他的間接方式或手段才能夠體現出其實際上的效果。
那麼在C++中就是採用了這個詞意,不可以在語言模型中直接調用或體現的,但是確實是存在可以被間接的方式進行調用或體現的。比如:虛函數必須要通過一種間接的運行時(而不是編譯時)機制才能夠激活(調用)的函數,而虛繼承也是必須在運行時才能夠進行定位訪問的一種體制。存在,但間接。其中關鍵就在於存在、間接和共享這三種特徵。
對於虛函數而言,這三個特徵是很好理解的,間接性表明了他必須在運行時根據實際的對象來完成函數定址,共享性表象在基類會共享被子類重載後的虛函數,其實指向相同的函數入口。
對於虛繼承而言,這三個特徵如何理解呢?存在即表示虛繼承體系和虛基類確實存在,間接性表明了在訪問虛基類的成員時同樣也必須通過某種間接機制來完成(下面模型中會講到),共享性表象在虛基類會在虛繼承體系中被共享,而不會出現多份拷貝。
那現在可以解釋語法小節中留下來的那個問題了,「為什麼一旦出現了虛基類,就必須在每一個繼承類中都必須包含虛基類的初始化語句」。由上面的分析可以知道,
虛基類是被共享的,也就是在繼承體系中無論被繼承多少次,對象內存模型中均只會出現一個虛基類的子對象(這和多繼承是完全不同的),這樣一來既然是共享的那麼每一個子類都不會獨占,但是總還是必須要有一個類來完成基類的初始化過程(因為所有的對象都必須被初始化,哪怕是默認的),同時還不能夠重復進行初始化,那到底誰應該負責完成初始化呢?C++標准中(也是很自然的)選擇在每一次繼承子類中都必須書寫初始化語句(因為每一次繼承子類可能都會用來定義對象),而在最下層繼承子類中實際執行初始化過程。所以上面在每一個繼承類中都要書寫初始化語句,但是在創建對象時,而僅僅會在創建對象用的類構造函數中實際的執行初始化語句,其他的初始化語句都會被壓制不調用。 為了實現上面所說的三種語義含義,在考慮對象的實現模型(也就是內存模型)時就很自然了。在C++中對象實際上就是一個連續的地址空間的語義代表,我們來分析虛繼承下的內存模型。
3.1. 存在
也就是說在對象內存中必須要包含虛基類的完整子對象,以便能夠完成通過地址完成對象的標識。那麼至於虛基類的子對象會存放在對象的那個位置(頭、中間、尾部)則由各個編譯器選擇,沒有差別。(在VC8中無論虛基類被聲明在什麼位置,虛基類的子對象都會被放置在對象內存的尾部)
3.2. 間接
間接性表明了在直接虛基承子類中一定包含了某種指針(偏移或表格)來完成通過子類訪問虛基類子對象(或成員)的間接手段(因為虛基類子對象是共享的,沒有確定關系),至於採用何種手段由編譯器選擇。(在VC8中在子類中放置了一個虛基類指針vbc,該指針指向虛函數表中的一個slot,該slot中存放則虛基類子對象的偏移量的負值,實際上就是個以補碼表示的int類型的值,在計算虛基類子對象首地址時,需要將該偏移量取絕對值相加,這個主要是為了和虛表中只能存放虛函數地址這一要求相區別,因為地址是原碼表示的無符號int類型的值)
3.3. 共享
共享表明了在對象的內存空間中僅僅能夠包含一份虛基類的子對象,並且通過某種間接的機制來完成共享的引用關系。在介紹完整個內容後會附上測試代碼,體現這些內容。 由於有了間接性和共享性兩個特徵,所以決定了虛繼承體系下的對象在訪問時必然會在時間和空間上與一般情況有較大不同。
4.1. 時間
在通過繼承類對象訪問虛基類對象中的成員(包括數據成員和函數成員)時,都必須通過某種間接引用來完成,這樣會增加引用定址時間(就和虛函數一樣),其實就是調整this指針以指向虛基類對象,只不過這個調整是運行時間接完成的。(在VC8中通過打開匯編輸出,可以查看*.cod文件中的內容,在訪問虛基類對象成員時會形成三條mov間接定址語句,而在訪問一般繼承類對象時僅僅只有一條mov常量直接定址語句)
4.2. 空間
由於共享所以不存在對象內存中保存多份虛基類子對象的拷貝,這樣較之多繼承節省空間。
⑤ C++ 虛基類的問題。。。
c.A::x = 10; 可以這樣修改。
編譯的提示錯誤是 x數據成員不明確,警告是,無法判斷是類A或類B的數據成員。
因為你虛繼承的2個類中,數據成員的變數名是相同的,使用時需加上類操作符限定。
⑥ C++虛基類問題
你所說的車這個「虛基類」並不是一個實現類,也不是類的實例。
其實你可以這么理解:
汽車、自行車、摩托車這幾個類之間並沒有什麼聯系,是完全不同的類。只不過有相同的屬性而已,例如:汽車有汽車的重量和自行車有自行車的重量,而車是凌駕於「各種車們」之上的概念,它其實是與汽車、自行車、摩托車類一樣,都是類。他們唯一的關系是:定義汽車的重量時用到了車的重量這個概念,都繼承於車,證明他們都有車的共同屬性,但各自的重量可以不同,汽車是1T而自行車是2kg。
總體來說就是,這些汽車、自行車、**車...他們都有這些屬性,但屬性值可以不同!
上升點高度:子類繼承於父類後,會繼承父類的屬性,而不會繼承父類的屬性值,同樣,各子類間的屬性值沒有任何關系。
不知道講明白沒~~
⑦ C++虛基類 問題
虛基類是相對於它的派生類而言的,它本身可以是一個普通的類。
只有它的派生類虛繼承它的時候,它才稱作虛基類,如果沒有虛繼承的話,就稱為基類。比如類b虛繼承於類a,那類a就稱作類b的虛基類,如果沒有虛繼承,那類b就只是類a的基類。
虛繼承主要用於一個類繼承多個類的情況,避免重復繼承同一個類兩次或多次。
例如
由類a派生類b和類c,類d又同時繼承類b和類c,這時候類d就要用虛繼承的方式避免重復繼承類a兩次。
⑧ 虛基類在解決二義性中的問題中的作用是什麼
虛基類用於某類從多個類繼承,這多個基類有共同基類時,這個最上層基類的成員會多次在最終派生類出現而產生二義性,為避免二義性,使得最終派生類中,最上層的基類成員只有一份,這時需要虛擬繼承,該最上層類就是虛基類,需要注意的是,該類在第一層派生時就要虛擬繼承才行,使用方法是在繼承方式前加上一個 virtual就可以了。
找個程序給你看一下:
1.為解決多繼承產生的二義性問題,可以使用虛基類來消除二義性問題、如:
//在最後的派生類中,不僅要負責對直接基類進行初始化,還要負責對虛基類進行初始化
//編譯系統只執行最後的派生類對虛基類的構造函數的調用,而忽略虛基類的其他派生類對虛基類的構造函數的調用,這就保證了虛基類的//數據成員不會被多次初始化、
#include<iostream>
#include<string>
using namespace std;
class person
{
protected:
string name;
char sex;
int age;
public:
person(string nam,char s,int a):name(nam),sex(s),age(a)
{ }
};
class teacher:virtual public person
{
protected:
string title;
public:
teacher(string nam,char s,int a,string t):person(nam,s,a),title(t)
{ }
};
class student:virtual public person
{
protected:
float score;
public:
student(string nam,char s,int a,float sco):person(nam,s,a),score(sco)
{ }
};
class Graate:public teacher,public student
{
private:
float wawg;
public:
Graate(string nam,char s,int a,string t,float sco,float w):person(nam,s,a),teacher(nam,s,a,t),student(nam,s,a,sco),wawg(w){}
void show()
{
cout<<"name:"<<name<<endl;
cout<<"age:"<<age<<endl;
cout<<"sex:"<<sex<<endl;
cout<<"score:"<<score<<endl;
cout<<"title:"<<title<<endl;
cout<<"wawgs:"<<wawg<<endl;
}
};
int main()
{
Graate grad1("wang_li",'f',24,"assistent",89.5,1234.5);
grad1.show();
return 0;
}
2.利用一個指向基類的指針指向派生類的對象,看一下這個例子你就懂了。
#include<iostream>
#include<string>
using namespace std;
class student
{
private:
int num;
string name;
float score;
public:
student(int,string,float);
void display();
};
student::student(int n,string nam,float s):num(n),name(nam),score(s)
{}
void student::display()
{
cout<<"student的display(函數)"<<endl;
cout<<endl<<"num : "<<num<<endl;
cout<<"name: "<<name<<endl;
cout<<"score: "<<score<<endl;
}
class Graate:public student
{
private:
float pay;
public:
Graate(int,string,float,float);
void display();
};
Graate::Graate(int n,string nam,float s,float p):student(n,nam,s),pay(p)
{}
void Graate::display()
{
cout<<"Graate的dispaay(函數)"<<endl;
student::display();
cout<<"pay="<<pay<<endl;
}
int main()
{
student stu1(1001,"li",87.5);
Graate grad1(2001,"wang",98.7,7865.4);
student *pt=&stu1;
pt->display();
cout<<"============================"<<endl;
pt=&grad1;
pt->display();
cout<<"============================"<<endl;
stu1=grad1;
stu1.display();
return 0;
}
在student 的void display();前面加上virtual在試試,你就會明白虛函數的作用。
⑨ A是B的虛基類,B是C的虛基類嗎
其實 不要管虛基類的定義說的有多麼復雜
記住 判斷是否為虛基類的 只有一個標准
就是繼承的時候 是不是有virtual
沒有 就不是
所以 B不是C的虛基類。
作為證明 其實也很簡單。
在B裡面寫一個成員。
然後 寫個C:public B
寫個D:public B
最後寫一個E:public C,public D
編譯會出錯 說明不是虛基類。
⑩ 什麼叫做虛基類,它有何作用
在多條繼承路徑上有一個公共的基類,在這些路徑中的某幾條匯合處,這個公共的基類就會產生多個實例(或多個副本),若只想保存這個基類的一個實例,可以將這個公共基類說明為虛基類。
虛繼承是面向對象編程中的一種技術,是指一個指定的基類,在繼承體系結構中,將其成員數據實例共享給也從這個基類型直接或間接派生的其它類。
虛擬繼承是多重繼承中特有的概念。虛擬基類是為解決多重繼承而出現的。
(10)虛基類的函數編譯嗎擴展閱讀
使用虛基類注意事項
(1) 一個類可以在一個類族中既被用作虛基類,也被用作非虛基類。
(2) 在派生類的對象中,同名的虛基類只產生一個虛基類子對象,而某個非虛基類產生各自的子對象。
(3) 虛基類子對象是由最遠派生類的構造函數通過調用虛基類的構造函數進行初始化的。
(4) 最遠派生類是指在繼承結構中建立對象時所指定的類。
(5) 派生類的構造函數的成員初始化列表中必須列出對虛基類構造函數的調用;如果未列出,則表示使用該虛基類的預設構造函數。
(6) 從虛基類直接或間接派生的派生類中的構造函數的成員初始化列表中都要列出對虛基類構造函數的調用。但僅僅用建立對象的最遠派生類的構造函數調用虛基類的構造函數,而該派生類的所有基類中列出的對虛基類的構造函數的調用在執行中被忽略,從而保證對虛基類子對象只初始化一次。
(7) 在一個成員初始化列表中同時出現對虛基類和非虛基類構造函數的調用時,虛基類的構造函數先於非虛基類的構造函數執行。