您好,登錄后才能下訂單哦!
這篇文章主要介紹“C++私有繼承與EBO實例分析”,在日常操作中,相信很多人在C++私有繼承與EBO實例分析問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”C++私有繼承與EBO實例分析”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
在此強調,這個標題中,第一個“繼承”指的是一種C++語法,也就是class A : B {};
這種寫法。而第二個“繼承”指的是OOP(面向對象編程)的理論,也就是A is a B的抽象關系,類似于“狗”繼承自“動物”的這種關系。
所以我們說,私有繼承本質是表示組合的,而不是繼承關系,要驗證這個說法,只需要做一個小實驗即可。我們知道最能體現繼承關系的應該就是多態了,如果父類指針能夠指向子類對象,那么即可實現多態效應。
請看下面的例程:
class Base {}; class A : public Base {}; class B : private Base {}; class C : protected Base {}; void Demo() { A a; B b; C c; Base *p = &a; // OK p = &b; // ERR p = &c; // ERR }
這里我們給Base類分別編寫了A、B、C三個子類,分別是public、private個protected繼承。然后用Base *類型的指針去分別指向a、b、c。發現只有public繼承的a對象可以用p直接指向,而b和c都會報這樣的錯:
Cannot cast 'B' to its private base class 'Base'
Cannot cast 'C' to its protected base class 'Base'
也就是說,私有繼承是不支持多態的,那么也就印證了,他并不是OOP理論中的“繼承關系”,但是,由于私有繼承會繼承成員變量,也就是可以通過b和c去使用a的成員,那么其實這是一種組合關系。或者,大家可以理解為,把b.a.member
改寫成了b.A::member
而已。
那么私有繼承既然是用來表示組合關系的,那我們為什么不直接用成員對象呢?為什么要使用私有繼承?這是因為用成員對象在某種情況下是有缺陷的。
在解釋私有繼承的意義之前,我們先來看一個問題,請看下面例程
class T {}; // sizeof(T) = ?
T是一個空類,里面什么都沒有,那么這時T的大小是多少?有的同學可能不假思索就會回答0。照理說,空類的大小就是應該是0,但如果真的設置為0的話,會有很嚴重的副作用,請看例程:
class T {}; void Demo() { T arr[10]; sizeof(arr); // 0 T *p = arr + 5; // 此時p==arr p++; // ++其實無效 }
發現了嗎?假如T的大小是0,那么T指針的偏移量就永遠是0,T類型的數組大小也將是0,而如果它成為了一個成員的話,問題會更嚴重:
struct Test { T t; int a; }; // t和a首地址相同
由于T是0大小,那么此時Test結構體中,t和a就會在同一首地址。
所以,為了避免這種0長的問題,編譯器會針對于空類自動補一個字節的大小,也就是說其實sizeof(T)是1,而不是0。
這里需要注意的是,不僅是絕對的空類會有這樣的問題,只要是不含有非靜態成員變量的類都有同樣的問題,例如下面例程中的幾個類都可以認為是空類:
class A {}; class B { static int m1; static int f(); }; class C { public: C(); ~C(); void f1(); double f2(int arg) const; };
有了自動補1字節,T的長度變成了1,那么T*的偏移量也會變成1,就不會出現0長的問題。但是,這么做就會引入另一個問題,請看例程:
class Empty {}; class Test { Empty m1; long m2; }; // sizeof(Test)==16
由于Empty是空類,編譯器補了1字節,所以此時m1是1字節,而m2是8字節,m1之后要進行字節對齊,因此Test變成了16字節。如果Test中出現了很多空類成員,這種問題就會被繼續放大。
這就是用成員對象來表示組合關系時,可能會出現的問題,而私有繼承就是為了解決這個問題的。
(EBO,Empty Base Class Optimization)
在上一節最后的歷程中,為了讓m1不再占用空間,但又能讓Test中繼承Empty類的其他內容(例如函數、類型重定義等),我們考慮將其改為繼承來實現,EBO就是說,當父類為空類的時候,子類中不會再去分配父類的空間,也就是說這種情況下編譯器不會再去補那1字節了,節省了空間。
但如果使用public繼承會怎么樣?
class Empty {}; class Test : public Empty { long m2; }; // 假如這里有一個函數讓傳Empty類對象 void f(const Empty &obj) {} // 那么下面的調用將會合法 void Demo() { Test t; f(t); // OK }
Test由于是Empty的子類,所以會觸發多態性,t會當做Empty類型傳入f中。這顯然問題很大呀!如果用這個例子看不出問題的話,我們換一個例子:
class Alloc { public: void *Create(); void Destroy(); }; class Vector : public Alloc { }; // 這個函數用來創建buffer void CreateBuffer(const Alloc &alloc) { void *buffer = alloc.Create(); // 調用分配器的Create方法創建空間 } void Demo() { Vector ve; // 這是一個容器 CreateBuffer(ve); // 語法上是可以通過的,但是顯然不合理 }
內存分配器往往就是個空類,因為它只提供一些方法,不提供具體成員。Vector是一個容器,如果這里用public繼承,那么容器將成為分配器的一種,然后調用CreateBuffer的時候可以傳一個容器進去,這顯然很不合理呀!
那么此時,用私有繼承就可以完美解決這個問題了
class Alloc { public: void *Create(); void Destroy(); }; class Vector : private Alloc { private: void *buffer; size_t size; // ... }; // 這個函數用來創建buffer void CreateBuffer(const Alloc &alloc) { void *buffer = alloc.Create(); // 調用分配器的Create方法創建空間 } void Demo() { Vector ve; // 這是一個容器 CreateBuffer(ve); // ERR,會報錯,私有繼承關系不可觸發多態 }
此時,由于私有繼承不可觸發多態,那么Vector就并不是Alloc的一種,也就是說,從OOP理論上來說,他們并不是繼承關系。而由于有了私有繼承,在Vector中可以調用Alloc里的方法以及類型重命名,所以這其實是一種組合關系。
而又因為EBO,所以也不用擔心Alloc占用Vector的成員空間的問題。
到此,關于“C++私有繼承與EBO實例分析”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。