c++中的菱形繼承與虛擬菱形繼承
c++中的繼承關係分為單繼承和多繼承
- 單繼承:一個派生類只有一個基類
- 多繼承:一個派生類不止有一個基類
在多繼承的過程成容易造成二義性問題。
菱形繼承是多繼承中的一種複雜的情況。
菱形繼承的有兩個問題:
- 二義性:當使用A的資料時無法確定繼承自B 還是 C
- 資料冗餘:類D中會有兩份類A的資料
先看一個菱形繼承的例子
#include <iostream> using namespace std; class A{ public: int _a = 1; }; class B : public A{ public: int _b = 2; }; class C : public A{ public: int _c = 3; }; class D : public B, public C { public: int _d = 4; }; int main(){ D d1; //d1._a = 100; d1.B::_a = 100; //可以顯示的指定訪問那一個類中的成員,解決了二義性問題 d1.C::_a = 101; }
D中最後會出現兩份_a,這就造成了資料冗餘,為了解決資料冗餘的問題,使用了虛擬繼承
l菱形虛擬繼承
通過菱形的虛擬繼承,可以使該多繼承體系中只有一份公共的A中的資料
//虛擬繼承通過virtual關鍵字實現 class A{ public: int _a = 1; }; class B : virtual public A{ public: int _b = 2; }; class C : virtual public A{ public: int _c = 3; }; class D : public B, public C { public: int _d = 4; };
在虛擬繼承中通過使用虛基表,來實現虛擬繼承不對原繼承體系的原本繼承關係造成影響。儘管現在只有一份公共的A,但原來的繼承關係並不發生改變,只要繼承了A都了能訪問A的資料,我們通過它們的記憶體模型來看
通過虛基表我們可以發現,此時物件d1中只有一個A中的資料_a,不再出現冗餘的兩份_a,而原來存放繼承自B中的_a的位置以及繼承自C中的_a的位置,此時存放的是一個地址,找到該地址,發現其中存放的是數,而這個數就是一個偏移量,表示此時B和C的位置到達公共部分_a的偏移量。這兩個地址叫做虛基表指標,而指向的就是虛基表,這兩個虛基表中存放的是偏移量。
繼承關係和組合關係
多繼承體現了c++的語法複雜性,算是c++的一個設計缺陷,因為多繼承的關係,所以有了菱形繼承,從而出現了虛擬繼承。從而導致了複雜度的上升,所以儘量少的使用多繼承。
組合
class Legs{
protected:
size_t _size = 17; // 腿長
};
class Person{
protected:
string _name ;//姓名
Legs _leg;
};
繼承體現的是設計層次結構中的複用,而還有一種物件間的服用關係,就是組合。
-
繼承表明了每一個派生類物件都是一個基類物件,是一種has-a的關係。
-
組合關係表示,如果B組合A,表明每一個B物件中都有一個A物件,是一種is-a的關係。
-優先使用組合關係,而不是繼承關係,表明了一個物件是組成另一個物件的一部分。 -
派生類與基類之間的關係又稱為白箱複用,白箱是指可視性而言,在繼承關係中基類內部的細節對於派生類來說是可見的,基類在一定程度上破壞了封裝,基類的改變在很大程度上會影響派生類。所以繼承關心間的關聯性很強,耦合度很高。
-
組合關係是一種黑箱複用,被組合的類細節在另一個類中是不不可見的,被組合的類只需要提供良好的介面即可。組合之間的關係的依賴程度低,耦合度低,更能保證封裝的完好性。
-
面向物件的設計中傾向於高內聚,低耦合的設計,所以繼承和組合關係間,優先使用自合