C++虛繼承小結

虛繼承對類的對象佈局的影響

要理解多重繼承情況中重複基類時爲什麼會出現訪問路徑不明確的編譯錯誤,需要了解繼承中類對象在內存中的佈局。在C++繼承中,子類會繼承父類的成員變量,因此在子類對象在內存中會包括來自父類的成員變量。實例代碼如下,輸出結果表明了每個對象在內存中所佔的大小。

#include<iostream>
using std::cout;
using std::endl;
class Base
{
protected:
int value;
public:
Base()
{
//cout<<"in Base"<<endl;
}
};
class DerivedA:protected Base
{
protected:
int valueA;
public:
DerivedA()
{
//cout<<"in DerivedA"<<endl;
}
};
class DerivedB: protected Base
{
protected:
int valueB;
public:
DerivedB()
{
//cout<<"in DerivedB"<<endl;
}
};
class MyClass:DerivedA
{
private:
int my_value;
public:
MyClass()
{
//cout<<"in MyClass"<<value<<endl;
}
};
int main()
{
Base base_obj;
DerivedA derA_obj;
MyClass my_obj;
cout<<"size of Base object "<<sizeof(base_obj)<<endl;
cout<<"size of DerivedA object "<<sizeof(derA_obj)<<endl;
cout<<"size of MyClass object "<<sizeof(my_obj)<<endl;
}

輸出結果如下

 

從類的定義結合這裏的輸出便不難明白,在子類對象中是包含了父類數據的,即在C++繼承中,一個子類的object所表現出來的東西,是其自己的members加上其基類的member的總和。示意圖如下(這裏只討論非靜態變量)

 

在單繼承的時候,訪問相關的數據成員時,只需要使用名字即可。但是,在多重繼承時,情況會變得複雜。因爲重複基類中,在子類中變量名是相同的。這時,如果直接使用名字去訪問,便會出現歧義性。看下面的代碼以及對應的輸出

#include<iostream>
using std::cout;
using std::endl;
class Base
{
protected:
int value;
public:
Base()
{
//cout<<"in Base"<<endl;
}
};
class DerivedA:protected Base
{
protected:
int valueA;
public:
DerivedA()
{
//cout<<"in DerivedA"<<endl;
}
};
class DerivedB: protected Base
{
protected:
int valueB;
public:
DerivedB()
{
//cout<<"in DerivedB"<<endl;
}
};
class MyClass:DerivedA,DerivedB
{
private:
int my_value;
public:
MyClass()
{
//cout<<"in MyClass"<<value<<endl;
}
};
int main()
{
Base base_obj;
DerivedA derA_obj;
MyClass my_obj;
cout<<"size of Base object "<<sizeof(base_obj)<<endl;
cout<<"size of DerivedA object "<<sizeof(derA_obj)<<endl;
cout<<"size of MyClass object "<<sizeof(my_obj)<<endl;
}

輸出如下

 

代碼的變化之處在於MyClass同時繼承了DerivedA和DerivedB。而my_obj在內存中的大小變成了20,比之前大了8.正好是增加了繼承至DerivedB中的數據部分的大小。上面情況中,my_obj在內存中的佈局示意圖如下

 

 

從圖中可以看到,來自Base基類的數據成員value重複出現了兩次。這也正是爲什麼在MyClass中直接訪問value時會出現訪問不明確的問題了。

那麼使用虛繼承後,對象的數據在內存中的佈局又是什麼樣子呢?按照預測,既然在my_obj中只有一份來自Base的value,那麼大小是否就是16呢?

代碼及輸出如下

#include<iostream>
using std::cout;
using std::endl;
class Base
{
protected:
int value;
public:
Base()
{
//cout<<"in Base"<<endl;
}
};
class DerivedA:protected virtual Base
{
protected:
int valueA;
public:
DerivedA()
{
//cout<<"in DerivedA"<<endl;
}
};
class DerivedB: protected virtual Base
{
protected:
int valueB;
public:
DerivedB()
{
//cout<<"in DerivedB"<<endl;
}
};
class MyClass:DerivedA,DerivedB
{
private:
int my_value;
public:
MyClass()
{
//cout<<"in MyClass"<<value<<endl;
}
};
int main()
{
Base base_obj;
DerivedA derA_obj;
DerivedB derB_obj;
MyClass my_obj;
cout<<"size of Base object "<<sizeof(base_obj)<<endl;
cout<<"size of DerivedA object "<<sizeof(derA_obj)<<endl;
cout<<"size of DerivedB object "<<sizeof(derB_obj)<<endl;
cout<<"size of MyClass object "<<sizeof(my_obj)<<endl;
};

輸出結果如下

 

可以看到,DerivedA和DerivedB對象的大小變成了12,而MyClass對象的大小則變成了24.似乎大大超出了我們的預料。這其實是由於編譯器在其中插入了一些東西用來尋找這個共享的基類數據所用而造成的。(來自《深度探索C++對象模型》第3章 侯捷譯)這樣理解,Class如果內含一個或多個虛基類子對象,那麼將被分割爲兩部分:一個不變部分和一個共享部分。不變局部中的數據,不管後繼如何衍化,總是擁有固定的offset,所以這一部分數據可以直接存取。至於共享局部,所表現的就是虛基類子對象。根據編譯其的不同,會有不同的方式去得到這部分的數據,但總體來說都是需要有一個指向這部分共享數據的指針。

示意圖如下

 

當然實際編譯器使用的技術比這個要複雜,這裏就不做詳細討論了。感興趣的朋友可以參見《深入探索C++對象模型》

本篇文章來源於 Linux公社網站(www.linuxidc.com)  原文鏈接:http://www.linuxidc.com/Linux/2012-11/74492p3.htm

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章