1. 程式人生 > >《Effective C++》讀書筆記 條款39:明智而審慎地使用private繼承

《Effective C++》讀書筆記 條款39:明智而審慎地使用private繼承

我們已經知道public繼承是is-a的關係,在public繼承的情況下一個派生類可以暗自轉換成一個基類,也就是在需要基類引數的地方傳遞派生類也是可以的,畢竟public繼承是is-a的關係。但是對於private繼承情況就不一樣了,先看下面這段程式碼。

class Student :private Person
{
};
void eat(const Person& p)
{

}
void study(const Student &s)
{

}
int main()
{
	Student s;
	Person p;
	//eat(s); //出錯error C2243: “型別轉換”: 從“Student *”到“const Person &”的轉換存在,但無法訪問
	study(s);
	return 0;
}

對於上面的student類,它private繼承自Person,但是我們給一個需要基類Person的函式eat傳遞派生了student發現是行不通的,編譯器報錯,上面那個錯誤是我在用VS2013執行時報的錯,這也就是說private繼承與public繼承是不同的,private繼承不是is-a關係。 

private繼承其實意味著implemented-in-terms-of(根據某物實現出)。private繼承意味著你要用基類已經具備的某些特性去實現派生類的一些功能。這個特點讓我們想到了複合,複合也有這一功能,那麼我們應該用哪一種呢?答案是儘量用複合,只在牽扯到protected成員和virtual函式時才用private。

如果我們設計一個類,要用到另一個類的某些特性,但我們設計的這個類又不能說是那個類,比如我們之前說過的用list實現set,我們不能說set是一個list,所以不能用public繼承,在上一次的講解中用的是複合,但是如果涉及到要使用虛擬函式和訪問protected成員的操作,複合就不能實現了,而這一講告訴我們可以用private繼承。但是這不涉及protected成員和virtual函式時,還是儘量用複合。那麼除此之外private繼承還有一個好處,我們來看下面這段程式碼:

#include <iostream>

class Person{
};
class Student :private Person
{
private:
	int ID;
};
class Worker
{
private:
	int ID;
	Person p;
};
int main()
{
	Person p;
	Student s;
	Worker w;
	std::cout << sizeof(p) << std::endl;  //1
	std::cout << sizeof(s) << std::endl;  //4
	std::cout << sizeof(w) << std::endl;  //8
	return 0;
}

首先,一個空類的大小不是0,是1,這主要是因為面對“大小為0的獨立(非附屬)物件”,通常C++官方勒令默默安插一個char到空物件內。所以sizeof(p)是1,而對於sizeof(w)由於存在記憶體對齊,編譯器為其補充了3個空格,所以是8,但是如果是繼承,因為不是獨立物件了,C++就不進行默默安插char的操作了,所以對於sizeof(s),由於Person是空類,所以為4。這是所謂的EBO(empty base optimization:空白基類最優化)。EBO只在單一繼承(而非多重繼承)下才可行。

因為複合比較容易理解,所以無論什麼時候,只要可以,選擇複合而不是private繼承。

請記住:

1.Private繼承意味著is-implemented-in-terms-of(根據某物實現出),它通常比複合的級別低(意思就是優先考慮複合)。但是當派生類需要訪問protected base class的成員,或需要重新定義繼承而來的virtual函式時,這麼設計是合理的。

2.和複合不同,private繼承可以是empty base最優化。這對致力於“物件尺寸最小化”的程式庫開發者而言,可能重要。