程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> 明智地使用private繼承 Effective C++筆記

明智地使用private繼承 Effective C++筆記

編輯:關於C++

 

Item 32提出public繼承表示is-a的關系,這是因為編譯器會在需要的時候將子類對象隱式轉換為父類對象。 然而private繼承則不然:

class Person { ... };
class Student: private Person { ... };     // inheritance is now private
void eat(const Person& p);                 // anyone can eat

Person p;                                  // p is a Person
Student s;                                 // s is a Student
eat(p);                                    // fine, p is a Person
eat(s);                                    // error! a Student isn't a Person

Person可以eat,但Student卻不能eat。這是private繼承和public繼承的不同之處:

  • 編譯器不會把子類對象轉換為父類對象
  • 父類成員(即使是public、protected)都變成了private

    子類繼承了父類的實現,而沒有繼承任何接口(因為public成員都變成private了)。 因此private繼承是軟件實現中的概念,與軟件設計無關。 private繼承和對象組合類似,都可以表示is-implemented-in-terms-with的關系。那麼它們有什麼區別呢? 在面向對象設計中,對象組合往往比繼承提供更大的靈活性,只要可以使用對象組合就不要用private繼承。

    private繼承

    我們的Widget類需要執行周期性任務,於是希望繼承Timer的實現。 因為Widget不是一個Timer,所以我們選擇了private繼承:

    class Timer {
    public:
       explicit Timer(int tickFrequency);
       virtual void onTick() const;          // automatically called for each tick
    };
    class Widget: private Timer {
    private:
      virtual void onTick() const;           // look at Widget usage data, etc.
    };
    

    Widget中重寫虛函數onTick,使得Widget可以周期性地執行某個任務。為什麼Widget要把onTick聲明為private呢? 因為onTick只是Widget的內部實現而非公共接口,我們不希望客戶調用它(Item 18指出接口應設計得不易被誤用)。

    private繼承的實現非常簡單,而且有時只能使用private繼承:

    1. Widget需要訪問Timer的protected成員時。因為對象組合後只能訪問public成員,而private繼承後可以訪問protected成員。
    2. Widget需要重寫Timer的虛函數時。比如上面的例子中,由於需要重寫onTick單純的對象組合是做不到的。

      對象組合

      我們知道對象組合也可以表達is-implemented-in-terms-of的關系, 上面的需求當然也可以使用對象組合的方式實現。但由於需要重寫(override)Timer的虛函數,所以還是需要一個繼承關系的:

      class Widget {
      private:
          class WidgetTimer: public Timer {
          public:
              virtual void onTick() const;
          };
          WidgetTimer timer;
      };
      

      內部類WidgetTimerpublic繼承自Timer,然後在Widget中保存一個WidgetTimer對象。 這是public繼承+對象組合的方式,比private繼承略為復雜。但對象組合仍然擁有它的好處:

      1. 你可能希望禁止Widget的子類重定義onTick。在Java中可以使用finel關鍵字,在C#中可以使用sealed。 在C++中雖然沒有這些關鍵字,但你可以使用public繼承+對象組合的方式來做到這一點。上述例子便是。
      2. 減小WidgetTimer的編譯依賴。如果是private繼承,在定義Widget的文件中勢必需要引入#includetimer.h。 但如果采用對象組合的方式,你可以把WidgetTimer放到另一個文件中,在Widget中保存WidgetTimer的指針並聲明WidgetTimer即可, 見Item 31。

        EBO特性

        我們講雖然對象組合優於private繼承,但有些特殊情況下仍然可以選擇private繼承。 需要EBO(empty base optimization)的場景便是另一個特例。 由於技術原因,C++中的獨立空對象也必須擁有非零的大小,請看:

        class Empty {}; 
        class HoldsAnInt {
        private:
          int x;
          Empty e;        
        };
        

        Empty e是一個空對象,但你會發現sizeof(HoldsAnInt) > sizeof(int)。 因為C++中獨立空對象必須有非零大小,所以編譯器會在Empty裡面插入一個char,這樣Empty大小就是1。 由於字節對齊的原因,在多數編譯器中HoldsAnInt的大小通常為2*sizeof(int)。更多字節對齊和空對象大小的討論見Item 7。 但如果你繼承了Empty,情況便會不同:

        class HoldsAnInt: private Empty {
        private:
          int x;
        };
        

        這時sizeof(HoldsAnInt) == sizeof(int),這就是空基類優化(empty base optimization,EBO)。 當你需要EBO來減小對象大小時,可以使用private繼承的方式。

        繼承一個空對象有什麼用呢?雖然空對象不可以有非靜態成員,但它可以包含typedef, enum, 靜態成員,非虛函數 (因為虛函數的存在會導致一個徐函數指針,它將不再是空對象)。 STL就定義了很多有用的空對象,比如unary_function, binary_function等。

        總結

        • private繼承的語義是is-implemented-in-terms-of,通常不如對象組合。但有時卻是有用的:比如方法protected成員、重寫虛函數。
        • 不同於對象組合,private繼承可以應用EBO,庫的開發者可以用它來減小對象大小。

           

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved