麻豆小视频在线观看_中文黄色一级片_久久久成人精品_成片免费观看视频大全_午夜精品久久久久久久99热浪潮_成人一区二区三区四区

首頁 > 學院 > 開發設計 > 正文

Effective C++ 2e Item42

2019-09-10 09:07:13
字體:
來源:轉載
供稿:網友

條款42: 明智地使用私有繼承

條款35說明,C++將公有繼承視為 "是一個" 的關系。它是通過這個例子來證實的:假如某個類層次結構中,Student類從Person類公有繼承,為了使某個函數成功調用,編譯器可以在必要時隱式地將Student轉換為Person。這個例子很值得再看一遍,只是現在,公有繼承換成了私有繼承:

class Person { ... };

class Student:/t/t      // 這一次我們
 private Person { ... };/t   // 使用私有繼承

void dance(const Person& p);        // 每個人會跳舞

void study(const Student& s);       // 只有學生才學習


Person p;/t/t/t   // p是一個人
Student s;/t/t/t  // s是一個學生

dance(p);/t/t/t   // 正確, p是一個人

dance(s);/t/t/t   // 錯誤!一個學生不是一個人

很顯然,私有繼承的含義不是 "是一個",那它的含義是什么呢?

"別忙!" 你說。"在弄清含義之前,讓我們先看看行為。私有繼承有那些行為特征呢?" 那好吧。關于私有繼承的第一個規則正如你現在所看到的:和公有繼承相反,如果兩個類之間的繼承關系為私有,編譯器一般不會將派生類對象(如Student)轉換成基類對象(如Person)。這就是上面的代碼中為對象s調用dance會失敗的原因。第二個規則是,從私有基類繼承而來的成員都成為了派生類的私有成員,即使它們在基類中是保護或公有成員。行為特征就這些。

這為我們引出了私有繼承的含義:私有繼承意味著 "用...來實現"。如果使類D私有繼承于類B,這樣做是因為你想利用類B中已經存在的某些代碼,而不是因為類型B的對象和類型D的對象之間有什么概念上的關系。因而,私有繼承純粹是一種實現技術。用條款36引入的術語來說,私有繼承意味著只是繼承實現,接口會被忽略。如果D私有繼承于B,就是說D對象在實現中用到了B對象,僅此而已。私有繼承在軟件 "設計" 過程中毫無意義,只是在軟件 "實現" 時才有用。

私有繼承意味著 "用...來實現" 這一事實會給程序員帶來一點混淆,因為條款40指出,"分層" 也具有相同的含義。怎么在二者之間進行選擇呢?答案很簡單:盡可能地使用分層,必須時才使用私有繼承。什么時候必須呢?這往往是指有保護成員和/或虛函數介入的時候 ---- 但這個問題過一會兒再深入討論。

條款41提供了一種方法來寫一個Stack 模板,此模板生成的類保存不同類型的對象。你應該熟悉一下那個條款。模板是C++最有用的組成部分之一,但一旦開始經常性地使用它,你會發現,如果實例化一個模板一百次,你就可能實例化了那個模板的代碼一百次。例如Stack模板,構成Stack<int>成員函數的代碼和構成Stack<double>成員函數的代碼是完全分開的。有時這是不可避免的,但即使模板函數實際上可以共享代碼,這種代碼重復還是可能存在。這種目標代碼體積的增加有一個名字:模板導致的 "代碼膨脹"。這不是件好事。

對于某些類,可以采用通用指針來避免它。采用這種方法的類存儲的是指針,而不是對象,實現起來就是:

? 創建一個類,它存儲的是對象的void*指針。
? 創建另外一組類,其唯一目的是用來保證類型安全。這些類都借助第一步中的通用類來完成實際工作。

下面的例子使用了條款41中的非模板Stack類,不同的是這里存儲的是通用指針,而不是對象:

class GenericStack {
public:
 GenericStack();
 ~GenericStack();

 void push(void *object);
 void * pop();

 bool empty() const;

private:
 struct StackNode {
   void *data;/t/t    // 節點數據
   StackNode *next;/t       // 下一節點

   StackNode(void *newData, StackNode *nextNode)
   : data(newData), next(nextNode) {}
 };

 StackNode *top;/t/t/t  // 棧頂

 GenericStack(const GenericStack& rhs);   // 防止拷貝和
 GenericStack&/t/t/t    // 賦值(參見
   operator=(const GenericStack& rhs);    // 條款27)
};


因為這個類存儲的是指針而不是對象,就有可能出現一個對象被多個堆棧指向的情況(即,被壓入到多個堆棧)。所以極其重要的一點是,pop和類的析構函數銷毀任何StackNode對象時,都不能刪除data指針 ---- 雖然還是得要刪除StackNode對象本身。畢竟,StackNode 對象是在GenericStack類內部分配的,所以還是得在類的內部釋放。所以,條款41中Stack類的實現幾乎完全滿足the GenericStack的要求。僅有的改變只是用void*來替換T。

僅僅有GenericStack這一個類是沒有什么用處的,但很多人會很容易誤用它。例如,對于一個用來保存int的堆棧,一個用戶會錯誤地將一個指向Cat對象的指針壓入到這個堆棧中,但編譯卻會通過,因為對void*參數來說,指針就是指針。

為了重新獲得你所習慣的類型安全,就要為GenericStack創建接口類(interface class),象這樣:

class IntStack {/t/t  // int接口類
public:
 void push(int *intPtr) { s.push(intPtr); }
 int * pop() { return static_cast<int*>(s.pop()); }
 bool empty() const { return s.empty(); }

private:
 GenericStack s;/t/t // 實現
};

class CatStack {/t/t  // cat接口類
public:
 void push(Cat *catPtr) { s.push(catPtr); }
 Cat * pop() { return static_cast<Cat*>(s.pop()); }
 bool empty() const { return s.empty(); }

private:
 GenericStack s;/t/t // 實現
};

正如所看到的,IntStack和CatStack只是適用于特定類型。只有int指針可以被壓入或彈出IntStack,只有Cat指針可以被壓入或彈出CatStack。IntStack和CatStack都通過GenericStack類來實現,這種關系是通過分層(參見條款40)來體現的,IntStack和CatStack將共享GenericStack中真正實現它們行為的函數代碼。另外,IntStack和CatStack所有成員函數是(隱式)內聯函數,這意味著使用這些接口類所帶來的開銷幾乎是零。

但如果有些用戶沒認識到這一點怎么辦?如果他們錯誤地認為使用GenericStack更高效,或者,如果他們魯莽而輕率地認為類型安全不重要,那該怎么辦?怎么才能阻止他們繞過IntStack和CatStack而直接使用GenericStack(這會讓他們很容易地犯類型錯誤,而這正是設計C++所要特別避免的)呢?

沒辦法!沒辦法防止。但,也許應該有什么辦法。

在本條款的開始我就提到,要表示類之間 "用...來實現" 的關系,有一個選擇是通過私有繼承。現在這種情況下,這一技術就比分層更有優勢,因為通過它可以讓你告訴別人:GenericStack使用起來不安全,它只能用來實現其它的類。具體做法是將GenericStack的成員函數聲明為保護類型:

class GenericStack {
protected:
 GenericStack();
 ~GenericStack();

 void push(void *object);
 void * pop();

 bool empty() const;

private:
 .../t/t/t     // 同上
};

GenericStack s;/t/t   // 錯誤! 構造函數被保護


class IntStack: private GenericStack {
public:
 void push(int *intPtr) { GenericStack::push(intPtr); }
 int * pop() { return static_cast<int*>(GenericStack::pop()); }
 bool empty() const { return GenericStack::empty(); }
};

class CatStack: private GenericStack {
public:
 void push(Cat *catPtr) { GenericStack::push(catPtr); }
 Cat * pop() { return static_cast<Cat*>(GenericStack::pop()); }
 bool empty() const { return GenericStack::empty(); }
};

IntStack is;/t/t     // 正確

CatStack cs;/t/t     // 也正確

和分層的方法一樣,基于私有繼承的實現避免了代碼重復,因為這個類型安全的接口類只包含有對GenericStack函數的內聯調用。

在GenericStack類之上構筑類型安全的接口是個很花俏的技巧,但需要手工去寫所有那些接口類是件很煩的事。幸運的是,你不必這樣。你可以讓模板來自動生成它們。下面是一個模板,它通過私有繼承來生成類型安全的堆棧接口:

template<class T>
class Stack: private GenericStack {
public:
 void push(T *objectPtr) { GenericStack::push(objectPtr); }
 T * pop() { return static_cast<T*>(GenericStack::pop()); }
 bool empty() const { return GenericStack::empty(); }
};

這是一段令人驚嘆的代碼,雖然你可能一時還沒意識到。因為這是一個模板,編譯器將根據你的需要自動生成所有的接口類。因為這些類是類型安全的,用戶類型錯誤在編譯期間就能發現。因為GenericStack的成員函數是保護類型,并且接口類把GenericStack作為私有基類來使用,用戶將不可能繞過接口類。因為每個接口類成員函數被(隱式)聲明為inline,使用這些類型安全的類時不會帶來運行開銷;生成的代碼就象用戶直接使用GenericStack來編寫的一樣(假設編譯器滿足了inline請求 ---- 參見條款33)。因為GenericStack使用了void*指針,操作堆棧的代碼就只需要一份,而不管程序中使用了多少不同類型的堆棧。簡而言之,這個設計使代碼達到了最高的效率和最高的類型安全。很難做得比這更好。

本書的基本認識之一是,C++的各種特性是以非凡的方式相互作用的。這個例子,我希望你能同意,確實是非凡的。

從這個例子中可以發現,如果使用分層,就達不到這樣的效果。只有繼承才能訪問保護成員,只有繼承才使得虛函數可以重新被定義。(虛函數的存在會引發私有繼承的使用,例子參見條款43)因為存在虛函數和保護成員,有時私有繼承是表達類之間 "用...來實現" 關系的唯一有效途徑。所以,當私有繼承是你可以使用的最合適的實現方法時,就要大膽地使用它。同時,廣泛意義上來說,分層是應該優先采用的技術,所以只要有可能,就要盡量使用它。

上一篇:確認Buffer

下一篇:Foo 的辭源

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表

圖片精選

主站蜘蛛池模板: 国内成人自拍视频 | 九九热在线视频免费观看 | 免费国产成人高清在线看软件 | 欧美成人做爰高潮片免费视频 | 国产一级淫片免费看 | 国产91在线亚洲 | 天天看夜夜爽 | 91久久国产露脸精品国产 | 特色一级黄色片 | 免费一级在线观看 | 国产视频在线观看一区二区三区 | h色网站免费观看 | 中午字幕无线码一区2020 | 成人国产精品一区 | 天天透天天狠天天爱综合97 | 欧美精品久久久久久久久久 | 成人在线视频一区 | 欧美一区2区三区4区公司二百 | 污视频在线免费播放 | 国产精品野外av久久久 | 99精品国产一区二区三区 | 精品一区二区6 | xxxx69hd一hd72| 精品黑人一区二区三区国语馆 | 国产1区2区3区中文字幕 | 日本搞逼视频 | 羞羞视频免费视频欧美 | 成人免费网站在线观看视频 | 亚洲第一综合 | 毛片免费看的 | 视频在线色 | 精品亚洲免费 | 精品一区二区在线观看 | 久久色伦理资源站 | 最新中文字幕在线 | 日本精品二区 | 久久久久一区二区三区 | 久久久www成人免费精品 | 一级片a | 九九热久久免费视频 | 国产精品av久久久久久网址 |