標籤:bsp 中繼 其他 拷貝構造 code 定義 參數 導致 ret
第六章 繼承和物件導向設計
條款35: 使公有繼承體現 "是一個" 的含義
1.子類對象一定“是一個”基類對象,基類對象不一定是子類對象
條款36: 區分介面繼承和實現繼承
1.希望衍生類別只繼承成員函數的介面——將函式宣告為純虛函數
2.希望衍生類別同時繼承函數的介面和實現,但允許衍生類別改寫實現——將函式宣告為虛函數
3.希望同時繼承介面和實現,並且不允許衍生類別改寫任何東西——聲明為非虛函數
條款37: 決不要重新定義繼承而來的非虛函數
1.基類聲明為非虛的函數表示這個函數不希望被子類所修改,而是直接被子類繼承使用
2.非虛函數是靜態繫結的,當將一個子類對象通過基類指標的形式調用時,並不會調用子類對象所重寫的函數——基類的非虛函數被調用
3.如果子類重寫了基類的非虛函數,則分別通過子類對象和指標調用時會顯得有些精神分裂——子類對象調用該函數時調用的是子類的,如果通過幾類的指標調用,則會調用基類的非虛函數
條款38: 決不要重新定義繼承而來的預設參數值
1.由於重定義繼承而來的非虛函數是一種錯誤,因此此條相當於不要“繼承一個有預設參數值的虛函數”
2.虛函數是動態綁定而預設參數值是靜態繫結的,當一個子類對象通過基類指標的形式調用一個被繼承而來的有預設值的函數時,子類的函數被調用,但是預設值確實基類的!
條款39: 避免 "向下轉換" 繼承層次
1.如果將子類的指標轉換成基類後要調用子類中一個不是從基類繼承的函數或者是繼承的非虛函數,此時只能通過強制類型的靜態轉換來實現——不到萬不得已的時候千萬不要使用向下轉換,會使代碼變得不宜維護
2.第1點最好的解決辦法是通過虛函數繼承來解決
3.如果無法通過虛函數繼承來解決第一點中的問題,盡量使用動態轉換——dynamic_cast,它可以決絕轉換失敗的問題,當轉換失敗時返回一個NULL指標
條款41: 區分繼承和模板
1.當對象的類型不影響類中函數的行為時,就要使用模板來產生這樣一組類
2.當對象的類型影響類中函數的行為時,就要使用繼承來得到這樣一組類
條款42: 明智地使用私人繼承
1.和公有繼承相反,如果兩個類之間的繼承關係為私人,編譯器一般不會將衍生類別對象轉換成基類對象
2.從私人基類繼承而來的成員都成為了衍生類別的私人成員,即使它們在基類中是保護或公有成員
3.如果有一個通用但不安全的類,為了防止使用者濫用帶來的隱患,可以將該類設計為要給基類,將其所有介面和成員變數聲明為protected,然後讓其他特定的類來私人繼承該類
class GenericStack {protected: GenericStack(); ~GenericStack(); void push(void *object); void * pop(); bool empty() const;private: ... // 同上};GenericStack s; // 錯誤! 建構函式被保護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; // 正確CatStack cs; // 也正確
條款43: 明智地使用多繼承
1.如果一個類從多個類繼承而來,且其基類中存在相同的成員函數或者成員變數,但是子類又沒有定義該介面或變數,則通過子類調用時會產生二義性
class Lottery {public: virtual int draw(); ...};class GraphicalObject {public: virtual int draw(); ...};class LotterySimulation: public Lottery,public GraphicalObject { ... // 沒有聲明draw};LotterySimulation *pls = new LotterySimulation;pls->draw(); // 錯誤! ---- 二義pls->Lottery::draw(); // 正確——當顯式地用一個類名來限制修飾一個虛函數時,函數的行為將不再具有虛擬特徵。相反,被調用的函數只能是你所指定的那個,即使調用是作用在衍生類別的對象上pls->GraphicalObject::draw(); // 正確
2.如果一個classD類從類classA和classB繼承而來,且類classA和classB都定義了相同名稱的虛函數func,當子類需要重寫class A和Aclass B的2個func時,將發生二義性——一個類只允許一個同名的同參數的函數,此時可以通過增加中介層來解決這個問題
以下是一個集純虛函數,簡單虛函數和內嵌函式(參見條款33)綜合應用之大成的方法,值得牢記在心:
class Lottery {public: virtual int draw(); ...};class GraphicalObject {public: virtual int draw(); ...};class AuxLottery: public Lottery {public: virtual int lotteryDraw() = 0; virtual int draw() { return lotteryDraw(); }};class AuxGraphicalObject: public GraphicalObject {public: virtual int graphicalObjectDraw() = 0; virtual int draw() { return graphicalObjectDraw(); }};class LotterySimulation: public AuxLottery, public AuxGraphicalObject {public: virtual int lotteryDraw(); virtual int graphicalObjectDraw(); ...};
LotterySimulation *pls = new LotterySimulation;
Lottery *pl = pls;
GraphicalObject *pgo = pls;
// 調用LotterySimulation::lotteryDraw
pl->draw();
// 調用LotterySimulation::graphicalObjectDraw
pgo->draw();
3.如果子類從虛基類中繼承而來,則基類的初始化參數是由子類建構函式的參數傳遞進來的,當繼承層次較深將導致參數的多次傳遞,如果中間的繼承關係被修改,則執行初始化的類(離虛基類最近的類)還可能改變,解決的辦法是,不要在虛基類中聲明成員變數,僅僅提供介面(java的做法,其介面——虛基類禁止包含資料)
4.磚石繼承的二義性:有類class A class B class C class D,B和C繼承自A,D繼承自B和C。如果A和C中定義了虛函數mf,B和D未定義,通過D的對象調用mf時,可能會產生二義性:
1.如果A是虛基類,則由於C重寫了mf,其優先順序高於A的mf,因此C的mf被調用
2.如果A不是虛基類,則將產生二義性——因為函數mf不具備多態性,通過D調用mf時將難以在A和C之間取捨
第七章 雜項
條款45: 弄清C++在幕後為你所寫、所調用的函數
1.一個空類,C++編譯器至少為它提供了一個拷貝建構函式、一個賦值雲演算法、一個解構函式、一個取址雲演算法、一個建構函式,所有這些函數都是共有的
2.除非這個類的基類的解構函式是虛函數,否則編譯器提供的解構函式是非虛函數
3.預設的拷貝建構函式和賦值雲演算法對類的非待用資料成員進行”以成員為單位的“逐一拷貝(賦值)
4.對於類的非靜態成員變數,預設的拷貝建構函式和賦值運算子調用其成員變數類的拷貝建構函式或者賦值運算子
5.如果不想實現operator = 或 operator&,可以將其聲明為private
【C++常識】effective C++ 使用規定——記憶體管理/繼承和物件導向設計/雜項