“瑜珈山夜話”— 尋根究底談“繼承”(一)

來源:互聯網
上載者:User

    摘要:繼承是C++的一個很重要的特性,也是OO的三大特徵之一,希望對此做一個簡單的論述,能消除你一些困惑。
    
    繼承是什嗎?
    繼承是將相關的類組織起來,並分亨其間的共通資料和操作行為的一種方法,同時也要注意到繼承關係是一種強耦合的關係。
  
    繼承的目的是什嗎?
    說到繼承的目的,人們總是會想到代碼重用,實則不然,代碼重用只不過是繼承的一個副作用,繼承的主要目的是表達一個外部有意義的關係,該關係描述了問題域內的2個實體之間的行為關係。換句話說,繼承是因問題域的現實性而產生的,並不是由於解域內的技術目的而出現的。

    繼承的障礙是什嗎?
    繼承的使用並不像我們想象的那麼簡單,在決定繼承的時候,有很多語言特性會構成一定的障礙。
    1、非虛成員函數的存在。
    如果我們確定了一個基類中的某個成員函數是非虛的,那就意味著這個函數在衍生類別中不應該被重新定義,如果你重新定義了,所得的結果很可能不是你所期望的,例如:
     class A
     {  public: void f() { cout<<"A::f"<<endl; }    };
     class B: public A
     {  public: void f() { cout<<"B::f"<<endl; }    };
     A* pA=new B;
     pA->f();
     delete pA;
     這裡,我們可能期望pA->f()會輸出B::f,但是實際上是A::f,當然,如果把它聲明為virtual就沒有問題了,關鍵是我們怎麼能夠明確確定那個函數應該聲明為virtual呢?如何使基類能夠完全預測到子類的各種需求?毫無疑問,這是一個挑戰!也許把所有的基類成員函數都聲明為virtual是一個簡單的解決辦法,但是這樣做會大大降低程式的執行效率,對於如此注重效率的C++來說,這麼做是對它的一個背叛,C++更希望我們只把那些需要重定義的函式宣告為virtual。
    2、基類成員的過度保護
    封裝是一個很好的特性,但是封裝的度很難掌握,例如:
    class A
    {   private: class P { ...};   };
    class B : public A::P { ... };
    有經驗的程式員馬上就會意識到這是一個錯誤:無法擷取A::P,因為它的許可權是Private!當然這裡只需要把private改為protected就可以了,但是問題的關鍵在於基類如何預測到子類需要繼承的類究竟是什嗎?同上一個障礙一樣,這也是一個挑戰。天真的程式員可能以為只要把基類中所有的成員都聲明為public/protected就萬事大吉了,但是實際上如果我們的類發布之後,public/protected的成員就再也無法改變,否則勢必會中斷客戶的代碼,這就要求我們盡量把實現細節封裝為private的,只把那些子類需要變動的成員聲明為public/protected許可權(虛函數可以聲明為private的,這是一個例外),但是對基類的設計者要求如此之高,也是非常困難的。
    3、基類中模組化設計不足
    模組化會使程式更加簡潔、有效,但是對於基類來說,要做到有效模組化並不容易。例如我們有一個二分尋找樹BSTree,定義如下:
    template<class T>
    class BSTree
    {
    private:
         class Node
         {
         public:
               T t;
               Node* left;
               Node* right;
               Node(const T& _t):t(_t){ }
               ...
         };
         Node* root;
         ...
    public:
        void insert(const T& t);
        ...
    protected:
        virtual void doinsert(const T& t, Node*& n);
        ...
    };
    template<class T>
    void BSTree<T>::doinsert(const T& t, Node*& n)
    {
        if(n==0) n=new Node(t);
        else
        {
             if(t<n->t) doinsert(t, n->left);
             else doinsert(t, n->right);
        }
     }
     現在呢,我們要定義一個紅/黑樹狀結構,定義如下:
     template<class T>
     class RBTree: public BSTree<T>
     {
     protected:
         class Node: public BSTree<T>::Node
         {
         public:
             bool is_red;
             Node(const T& t);
         };
         void doinsert(const T& t, BSTree<T>::Node*& n);
 virtual void rebalance(Node* n);
         ...
     };
     template<class T>
     void BSTree<T>::doinsert(const T& t, Node*& n)
     {
        if(n==0)
        {
             Node m=new Node(t);
     n=m;
             rebalance(m);
        }
        else
        {
             if(t<n->t) doinsert(t, n->left);
             else doinsert(t, n->right);
        }
     }
     我們發現BSTree::doinsert和RBTree::doinsert代碼大致相同,這就存在著複製代碼操作,我們知道代碼複製工作十分乏味、易出錯、代碼臃腫、維護困難...所以一個好的基類應該使衍生類別盡量少的複製代碼,最好不複製。看看我們的基類:很多二分尋找樹都需要建立不同的節點,也有rebalance操作。好了,我們應該對基類BSTree作如下修改:
     Template<class T>
     class BSTree
     {
     protected:
         virtual Node* new_node(const T& t)
         { return new Node(t); }
         virtual void rebalance(Node* n) { }
         ...
     };
     這時候doinsert改動如下:
     template<class T>
     void BSTree<T>::doinsert(const T& t, Node*& n)
     {
         if(n==0)
         {
              n=new_node(t);
              rebalance(n);
         }
         else
         {
             if(t<n->t) doinsert(t, n->left);
             else       doinsert(t, n->right);
         }
     }
     這時候衍生類別RBTree定義改為:
     template<class T>
     class RBTree: public BSTree<T>
     {
     protected:
          Node* new_node(const T& t)
          { return new Node(t); }
          void rebalance(BSTree<T>::Node* n)
          {  ...   }
          ...
     };
     這樣一來,程式員就無需複製代碼了。我們發現,如果要使衍生類別的客戶永遠不複製代碼,那麼就要把衍生類別需要改變的代碼分離出來,形成一個單獨的模組函數(虛),但是在我們沒有足夠的衍生類別的資訊的時候,這樣做是不可能的,就算可能,難度也是相當得高,同時,大量的虛函數也會降低程式的執行效率。 
    4、friend關鍵字的過分使用
    這個問題的根源在於友員關係的不繼承性。我們仍然用上面的例子,不過做一下變動:
    template<class T> class BSTree;
    template<class T>
    class BSNode
    {
     protected:
 T t;
         BSNode(const T& t);
         friend class BSTree<T>;
    };
    template<class T>
    class BSTree
    {
      ...沒有了nested Node類
    };
    這裡,由於BSNode的實現屬於BSTree的實現細節,同時為了防止BSNode衍生類別偶然存取BSNode的成員,所以我們把他的所有成員都聲明為Protected,同時讓BSTree稱為它的友員。但是由於RBTree要存取BSNode的成員,再加上友員的非繼承,使事情變得複雜起來,通常有2種辦法解決這個問題:
    1、將BSNode的成員聲明為public,但是這樣一來friend也就沒有什麼意義了。
    2、在RBNode類中增加一個存取函數,但是和不用friend相比,麻煩多了。
    另外還有一些其它的抉擇也是讓人頭疼,例如:基類中的成員變數過多,繼承的屬性選擇等。

未完(待續...)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.