<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
Hello!大家好呀,近期逗比老師的一個學生問了我這樣一個問題:“C++裡的私有繼承到底有什麼意義?”
不知道你有沒有跟他一樣的困惑。的確,我們在編寫C++專案中,幾乎是沒有用過私有繼承(這裡包括protected繼承和private繼承),都是清一色的public繼承。有的老師乾脆直接告訴學生,你見到繼承就是public,其他那倆是歷史原因,當它不存在就好了。
這種說法呢,其實也有一定道理,但也不全對。對的部分在於:C++中,確實只有public繼承才表示的OOP理論中的“繼承”,而私有繼承其實對應的是OOP理論中的“組合”關係,所以說“見到繼承就寫public”這話其實沒毛病。然而不對的部分在於:私有繼承是為了解決某些效能問題而存在的,我們知道通常表示組合的做法是成員物件,但在某些極端情況下,成員物件會出現一些效能問題,這時我們不得不用私有繼承來代替。
在此強調,這個標題中,第一個“繼承”指的是一種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的成員空間的問題。
總結下來,私有繼承其實是表示組合關係的,它是當組合類為空類時,為了增強效能而提供的一種成員物件的代替方案。
好啦!相信大家已經明白私有繼承的存在意義了,這裡建議大家閱讀一下STL原始碼,會看到絕大多數容器和分配器之間都是使用私有繼承方式的。如果還有什麼疑問歡迎評論區丟擲!
到此這篇關於C++私有繼承與EBO深入分析講解的文章就介紹到這了,更多相關C++私有繼承 內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45