關於private繼承

在C++的類中有許多種繼承方式,而我們在軟件設計和編寫代碼時用得最多的就是public繼承,我們很少接觸到private繼承。但是我們在設計時真的有思考過什麼時候應該用public繼承,什麼時候不該使用public繼承,什麼時候應該想想那些經常被我們遺忘的知識,讓我們從它們被遺忘的角落裏重拾它的光芒。例如private繼承。


As we all know,public繼承是塑模出一種is-a關係。什麼是is-a的關係呢?Effective C++這樣描述:如果你令class D以public形繼承class B,那麼每一個類型爲D的對象同時也是一個類型爲B的對象,反之不成立。意思是B比D表現出更一般化的概念,而D比B表現出更特殊化的概念。你主張“B對象可派上用場的地方,D對象一樣可以派上用場”,因爲每一個D對象都是一種(是一個)B對象。反之如果你需要一個D對象,B對象無法效勞。

還有就是我們喜歡用的virtual函數,即運行動態也是建立在public繼承之上的。

在我剛學C++時,只知道private繼承,使繼承而來的成員(public成員和protected成員)都成爲private,至於它有沒有什麼更深一層的意義和作用就沒有去思考了,只是理想當然地把它認爲是不想這些成員被它的以後的子類繼承。那private繼承到底意味着什麼呢?

先看看下面的代碼:
class Person{...};
class Student:private Person{...};
void eat(const Person &p);
Person p;
Student s;
eat(p);//正確
eat(s);//錯誤

再看看Effective C++的說法:
如果classes之間的繼承關係是private,編譯器不會自動將一個deirved class對象轉換爲一個base class對象。
private繼承意味着implemented-in-terms-of(根據某物實現出)。如果你讓class D以private繼承class B,你的用意是爲了採用class B內已經備妥的某些特性,不是因爲B對象和D對象存在有任何觀念上的關係。private繼承純粹是一種實現技術,意味着只有實現部分被繼承,接口部分應該略去。如果D以private形式繼承B,意思是D對象根據B對象實現而得,private繼承在軟件設計層面上沒有意義,其意義只及於軟件實現層面。

private有點has-a的感覺。它的意義與複合的意義相同。那我們的問題又產生了,什麼時候用複合什麼時候用private繼承?答案很簡單,儘可能使用複合。除了以下的2種情況:
1、private繼承通常比複合的級別低。但是當derived class需要訪問protected base class的成員,或需要重新定義繼承而來的virtual函數時,就要用private繼承。
2、private繼承可以造成empty class最優化。

#include <iostream>
using namespace std;
class Base
{
public:
    void Bfun1(){cout << "I'm Bfun1()" << endl;}
protected:
    void Bfun2(){cout << "I'm Bfun2()" << endl;}
private:
    void Bfun3(){cout << "I'm Bfun3()" << endl;}
};
class Derived : private Base
{
public:
    void Dfun1() {cout << "I'm Dfun1()  " ;Bfun1();}
    void Dfun2() {cout << "I'm Dfun2()  " ;Bfun2();}
};
int main()
{
    Derived d;
    d.Dfun1();
    d.Dfun2();

    //d.Bfun1();
    //d.Bfun2();
   // d.Bfun3();
    return 0;
}

運行後我們可以發現:1)派生類對象d是無法訪問基類的任一成員函數的(包括public成員函數),因爲這是private繼承

                                        2)派生類本身是能訪問基類的public 和protected成員的,如Dfun1函數訪問了Bfun1   Dfun2函數訪問了Bfun2;


二、關於《Effective c++》中Item 39

1、如果類之間的關係是private繼承,則:a:編譯器不會自動將一個派生類對象轉換成一個基類對象  b: 基類中的所有成員在派生類中都編程private屬性,派生類對象無法訪問

2、private表現出的關係是“根據某物實現出”,它繼承的是實現而非接口

     乍看很奇怪,實現都繼承了怎麼接口都不繼承。這樣理解,我們派生類需要利用private 基類中的一些成員實現某些東西,但是成員經過繼承後變成了private屬性,用戶是無法通過接口訪問它的,所以只是繼承了實現而忽略了接口。

3、private繼承主要用於:當兩個類不存在“is-a”的關係,其中一個需要訪問另一個的protected成員,或需要重新定義一個或多個virtual函數

這句話這麼理解:現有兩個類並非“is-a”關係,類D需要用類B的一些成員去實現它,而且我要用到的這些成員往往是類B的protected成員或者是虛函數。但是我只是需要這些成員去實現我的類D,並不給用戶提供接口能直接訪問類B的這些成員,這時候就應該用private繼承。

我們看一個例子吧,這是Item 40改編過來的。

我希望實現一個類CPerson 它能提取人的name 和 birthdate,我想偷個懶,發現有這麼一個類PersonInfo能實現差不多的功能,只是Person的格式有點不符合我的要求,我就打算利用PersonInfo來實現我Cperson的name函數和birthdate函數。

class PersonInfo{
	public:
		explicit PersonInfo(DatabaseID pid);
		virtual ~PersonInfo();
		virtual const char* theName() const;
		virtual const char* theBirthDate() const;
		...
	private:
		virtual const char* valueDelimOpen() const{return "["};	//“開始”符號,用於姓名的輸出
		virtual const char* valueDelimClose() const{return "]"};	//“結束”符號
		...
};
const char* PersonInfo::theName() const{
	static char value[Max_Formatted_Field_Value_Length];	//保留緩衝區給返回值使用。注意由於緩衝區是static的,會被自動初始化爲“全部是0”
	std::strcpy(value, valueDelimOpen());	//寫入起始符號
	...	//添加姓名
	std::strcat(value, valueDelimClose());	//寫入結束符號
	return value;
}
class CPerson : private PersonInfo{
public:

    std::string name()const
    {return PersonInfo::theName();}

    std::string birthDate()const
    {return PersonInfo::theBirthDate();}
private:
    virtual const char* valueDelimOpen() const{return ""};
    virtual const char* valueDelimClose() const{return ""};
    ...
};

我們發現:a:類CPerson的name通過調用類PersonInfo的成員函數實現,我這裏用了它的實現,卻沒有給用戶提供調用類PersonInfo成員的接口(因爲是private繼承)

                    b:由於PersonInfo的格式不符合我的要求,(它是用的方括號[    ],我卻不想要方括號),而控制格式的是兩個虛函數valueDelimOpen  valueDelimClose  這裏我就可以重定義virtual函數了,在Cperson中,我們重定義了上述兩個控制格式的函數。  這就是上面說的“需要重新定義一個或多個virtual函數”的情況


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