<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      [深入理解C++(二)]理解接口繼承規(guī)則

      [深入理解C++(二)]理解接口繼承規(guī)則

       羅朝輝 ( http://www.rzrgm.cn/kesalin/ )

      CC許可,轉(zhuǎn)載請注明出處

      一,前言

      在前一篇《[深入理解C++(一)]類型轉(zhuǎn)換(Type Casting)》中,我詳細講述了 C++ 中轉(zhuǎn)型動作,以及使用規(guī)則。有網(wǎng)友說應該提及下《深度探索 C++ 對象模型》一書中的內(nèi)容,其實他的意思是,要是對 C++ 對象的內(nèi)存布局不甚了解,就想要徹悟C++中的類型轉(zhuǎn)型,對象切割,虛函數(shù)調(diào)用等,猶如脫離了堅實的根基,想去建空中閣樓。理解 C++ 對象的內(nèi)存布局對學會 C++來說至關(guān)重要,但我不打算寫 C++ 對象的內(nèi)存布局相關(guān)的文章,因為要站在前人的肩膀上,大牛陳皓 已經(jīng)就這個主題寫了三篇圖文并茂的文章:

      (一),C++ 虛函數(shù)表解析

      (二),C++ 對象的內(nèi)存布局(上)

      (三),C++ 對象的內(nèi)存布局(下)

       

      在繼續(xù)閱讀本文之前,建議先閱讀這三篇文章,以更好地理解本系列文章。在接下來的內(nèi)容中,我將從重載,重寫,屏蔽等概念入手,引入眾多接口繼承規(guī)則。

       

      二,引子:重載(overload),重寫(override),屏蔽(hide)

      重載(overload):在相同作用域內(nèi),函數(shù)名稱相同,參數(shù)或常量性(const)不同的相關(guān)函數(shù)稱為重載。重載函數(shù)之間的區(qū)分主要在參數(shù)和常量性(const)的不同上,若僅僅是返回值或修飾符 virtual,public/protected/private的不同不被視為重載函數(shù)(無法通過編譯)。不同參數(shù)是指參數(shù)的個數(shù)或類型不同,而類型不同是指各類型之間不能進行隱身類型轉(zhuǎn)換或不多于一次的用戶自定義類型轉(zhuǎn)換(關(guān)于類型轉(zhuǎn)換,請參考前文:類型轉(zhuǎn)型(Type Casting))。當調(diào)用發(fā)生時,編譯器在進行重載決議時根據(jù)調(diào)用所提供的參數(shù)來選擇最佳匹配的函數(shù)。

      重寫(override):派生類重寫基類中同名同參數(shù)同返回值的函數(shù)(通常是虛函數(shù),這是推薦的做法)。同樣重寫的函數(shù)可以有不同的修飾符virtual,public/protected/private。

      屏蔽(hide):一個內(nèi)部作用域(派生類,嵌套類或名字空間)內(nèi)提供一個同名但不同參數(shù)或不同常量性(const)的函數(shù),使得外圍作用域的同名函數(shù)在內(nèi)部作用域不可見,編譯器在進行名字查找時將在內(nèi)部作用域找到該名字從而停止去外圍作用域查找,因而屏蔽外圍作用域的同名函數(shù)。

      (注:編譯器在決定哪一個函數(shù)應該被調(diào)用時,依次要做三件事:名字查找,重載決議,訪問性檢查。后續(xù)文章將詳細介紹這個決定過程。)

      下面來分析示例:

      class Base
      {
      public:
          virtual void f() { cout << "Base::f()" << endl; }
          void f(int) { cout << "Base::f(int)" << endl; }
          virtual void f(int) const { cout << "Base::f(int) const" << endl; }
          virtual void f(int *) { cout << "Base::f(int *)" << endl; }
      };
      
      class Derived : public Base
      {
      public:
          virtual void f() { cout << "Derived::f()" << endl; }
          virtual void f(char) { cout << "Derived::f(char)" << endl; }
      };
      const Base b;
      b.f(10);
      
      Derived d;
      int value = 10;
      
      d.f();
      d.f('A');
      d.f(10);
      //d.f(&value);//編譯報錯

       

      在上面代碼中,Base 中的一系列名為 f 的函數(shù)在同一作用域內(nèi),且同名不同參或不同常量性,故為重載函數(shù);而 Derived 中的 f() 則是重寫了基類同名同參的 f();而 Derived 中的 f(char) 則屏蔽了 Base 中所有的同名函數(shù)。

      所以上面代碼的執(zhí)行結(jié)果是:

      Base::f(int) const
      Derived::f()
      Derived::f(char)
      Derived::f(char)

      對 d.f(10); 這兩個調(diào)用,看似基類 Base 中有更好的匹配,但實際上由于編譯器在進行名字查找時,首先在 Derived 類作用域中進行查找,找到  f(char) 就停止去基類作用域中查找,因而基類的所有同名函數(shù)沒有機會進入重載決議,因而被屏蔽了。因此編譯器將 10 隱式轉(zhuǎn)型為 char 調(diào)用 Derived 中的 f(char)。至此,聰明的你應該很容易明白為什么 d.f(&value);  無法通過編譯了吧(VS編譯器的提示信息很給力)。

       

      三,函數(shù)繼承規(guī)則:

      鑒于繼承基類的函數(shù)有如此隱晦的概念需要弄懂,再加上 virtual 函數(shù),public/protected/private 繼承等等,更是增加了理解一個類接口的難度(因為你不僅要看類自身的接口,還有向上追溯所有基類的接口,以及是以何種方式繼承基類的接口等等)。因此,C++里面有很多針對類接口繼承的慣用法:

      1,優(yōu)先使用組合而非繼承。既然繼承代價如此之大,那么最好的就是不繼承唄。當然不是說完全不用繼承,只有在存在明確的“IS-A”關(guān)系時,繼承的好處才會顯現(xiàn)出來(可以用多態(tài)-但要遵循 Liskov 替換原則);而其他情況下(”HAS-A”或“Is-implemented-in-terms-of”)應毫不猶豫地使用組合,而且要優(yōu)先使用 PIMPL(Point to implementation) 手法(后續(xù)文章會介紹這個慣用法)來使用組合。

      2,純虛函數(shù)繼承規(guī)則-聲明純虛函數(shù)的目的是讓派生類來繼承函數(shù)接口而非實現(xiàn),使得純虛函數(shù)就像Java或C#中的 interface 一樣。唯一的例外就是需要純析構(gòu)函數(shù)提供實現(xiàn)(避免資源泄漏)。

      3,非純虛函數(shù)繼承規(guī)則-聲明非純虛函數(shù)的目的是讓派生類繼承函數(shù)接口及默認實現(xiàn)。但這是一種欠佳的做法,因為默認實現(xiàn)能讓新加入的沒有重寫該實現(xiàn)的派生類通過編譯并運行,而默認實現(xiàn)有可能并不適用于新加入的派生類,對此編譯器并不會提供任何信息(警告都沒一個)。為了應對這一潛在的陷阱,誕生了另一個規(guī)則:”純虛函數(shù)的聲明提供接口,純虛函數(shù)的實現(xiàn)提供默認實現(xiàn);派生類必須重寫該接口,但在實現(xiàn)時可以調(diào)用基類的默認實現(xiàn)。“

      如下代碼所示:

      class Base
      {
      public:
          virtual void f() = 0;
      };
      
      void Base::f()
      {
          cout << "Base::f() default implement." << endl;
      }
      
      class DerivedA : public Base
      {
      public:
          virtual void f()
          {
              Base::f();
          }
      };
      
      class DerivedB : public Base
      {
      public:
          virtual void f()
          {
              cout << "DerivedB::f() override." << endl;
          }
      };

       

      4,非虛函數(shù)繼承規(guī)則-永遠也不要重寫基類中的非虛函數(shù)。非虛函數(shù)的目的就是為了讓派生類繼承基類的強制性實現(xiàn),它并不希望被派生類改寫。

      5,盡量不要屏蔽外圍作用域(包括繼承而來的)名字。屏蔽所帶來的隱晦難以理解等問題在前面已有描述。

      如果沒得選擇(我還真沒想到有什么場景會出現(xiàn)這種情況,通常換個名字都是可行的)必須重新定義或重寫基類中同名函數(shù),那么你應該為每一個原本會被隱藏的名字引入一個 using 聲明或使用轉(zhuǎn)交函數(shù)(派生類定義同名同參函數(shù),在該函數(shù)內(nèi)部調(diào)用基類的同名同參函數(shù))來使這些名字在派生類的作用域中可見。(Effective C++ 條款33)。

      該規(guī)則應用如下:

      class Base
      {
      public:
          virtual void f() { cout << "Base::f()" << endl; }
          void f(int) { cout << "Base::f(int)" << endl; }
          virtual void f(int) const { cout << "Base::f(int) const" << endl; }
          virtual void f(int *) { cout << "Base::f(int *)" << endl; }
      };
      
      class Derived : public Base
      {
      public:
          using Base::f;
          virtual void f() { cout << "Derived::f()" << endl; }
          //virtual void f(char) { cout << "Derived::f(char)" << endl; }
      };
      const Base b;
      b.f(10);
      
      Derived d;
      int value = 10;
      
      d.f();
      d.f('A');
      d.f(10);
      d.f(&value);

      運行得到的結(jié)果為:

      Base::f(int) const
      Derived::f()
      Base::f(int)
      Base::f(int)
      Base::f(int *)

      在這里,因為使用了 using Base::f; ,因此基類中的所有名字 f 對子類來說都是可見的,所有 d.f(&value); 等均可通過編譯運行了。再次提醒:這是一種非常不好的做法。

      6,基類的析構(gòu)函數(shù)應當為虛函數(shù),以避免資源泄漏。

      假設(shè)有如下情況,帶非虛析構(gòu)函數(shù)的基類指針 pb 指向一個派生類對象 d,而派生類在其析構(gòu)函數(shù)中釋放了一些資源,如果我們 delete pb; 那么派生類對象的析構(gòu)函數(shù)就不會被調(diào)用,從而導致資源泄漏發(fā)生。因此,應該聲明基類的析構(gòu)函數(shù)為虛函數(shù)。

      7,避免 private 繼承 – private 繼承通常意味著根據(jù)某物實現(xiàn)出(Is-implemented-in-terms-of),此種情況下使用基類與派生類這樣的術(shù)語并不太合適,因為它不滿足 Liskov 替換原則,并且從基類繼承而來的所有接口均為私有的,外部不可訪問。private 繼承可用 PIMPL 手法取代。

      文中已經(jīng)兩次提到 PIMPL 利器,在這里就 private 繼承先給出一個示例,以后再詳述 PIMPL 的好處。

      原先使用 private 繼承:

      class SomeClass
      {
      public:
          void DoSomething(){}
      };
      
      class OtherClass : private SomeClass
      {
      private:
          void DoSomething(){}
      };

      使用 PIMPL 手法替代:

      class SomeClass
      {
      public:
          void DoSomething(){}
      };
      
      class OtherClass
      {
      public:
          OtherClass();
          ~OtherClass();
      
          void DoSomething();
      private:
          SomeClass * pImpl;
      };
      
      OtherClass::OtherClass()
      {
          pImpl = new SomeClass();
      }
      
      OtherClass::~OtherClass()
      {
          delete pImpl;
      }
      
      void OtherClass::DoSomething()
      {
          pImpl->DoSomething();
      }

       

      8,不要改寫繼承而來的缺省參數(shù)值。前面已經(jīng)說到非虛函數(shù)繼承是種不好的做法,所以在這里的焦點就放在繼承一個帶有缺省參數(shù)值的虛函數(shù)上了。為什么改寫繼承而來的缺省參數(shù)值不好呢?因為虛函數(shù)是動態(tài)綁定的,而缺省參數(shù)值卻是靜態(tài)綁定的,這樣你在進行多態(tài)調(diào)用時:函數(shù)是由動態(tài)類型決定的,而其缺省參數(shù)卻是由靜態(tài)類型決定的,違反直覺。

      有代碼有真相:

      class Base
      {
      public:
          // 前面的示例為了簡化代碼沒有遵循虛析構(gòu)函數(shù)規(guī)則,在這里說明下
          virtual ~Base() {}; 
          virtual void f(int defaultValue = 10)
          {
              cout << "Base::f() value = " << defaultValue << endl;
          }
      };
      
      class Derived : public Base
      {
      public:
          virtual void f(int defaultValue = 20)
          {
              cout << "Derived::f() value = " << defaultValue << endl;
          }
      };

      這段代碼的輸出為:

      Derived::f() value = 10

      調(diào)用的是動態(tài)類型 d -派生類 Derived的函數(shù)接口,但缺省參數(shù)值卻是由靜態(tài)類型 pb-基類 Base 的函數(shù)接口決定的,這等隱晦的細節(jié)很可能會浪費你一下午來調(diào)試,所以還是早點預防為好。

      9,還有一種流派認為不應公開(public)除虛析構(gòu)函數(shù)之外的虛函數(shù)接口,而應公開一個非虛函數(shù),在該非虛函數(shù)內(nèi) protected/private 的虛函數(shù)。這種做法是將接口何時被調(diào)用(非虛函數(shù))與接口如何被實現(xiàn)(虛函數(shù))分離開來,以達到更好的隔離效果。在設(shè)計模式上,這是一種策略模式。通常在非虛函數(shù)內(nèi)內(nèi)聯(lián)調(diào)用(直接在頭文件函數(shù)申明處實現(xiàn)就能達到此效果)虛函數(shù),所以在效率上與直接調(diào)用虛函數(shù)相比不相上下。

      譬如:

      class Base
      {
      public:
          virtual ~Base() {}
          
          void DoSomething()
          {
              StepOne();
              StepTwo();
          }
      private:
          virtual void StepOne() = 0;
          virtual void StepTwo() = 0;
      };
      
      class Derived : public Base
      {
      private:
          virtual void StepOne()
          {
              cout << "Derived StepOne: do something." << endl;
          }
          virtual void StepTwo()
          {
              cout << "Derived StepTwo: do something." << endl;
          }
      };

       

      四,后記

      C++ 陷阱特別多,學好用好 C++ 不容易,但只要把 OO 設(shè)計原則牢記在心頭,多見識些 C++ 慣用手法,C++ 的威力就能很好的展現(xiàn)出來。

       

      五,引用

      Effective C++ 條款 32 ~ 39

      More Effective C++ 條款20 ~ 25

      posted @ 2012-11-06 21:50  飄飄白云  閱讀(2307)  評論(2)    收藏  舉報
      本博客遵循 Creative Commons License “署名-非商業(yè)用途-保持一致”創(chuàng)作共用協(xié)議。 與我聯(lián)系
      主站蜘蛛池模板: 国产午夜A理论毛片| 性男女做视频观看网站| 国产亚洲av夜间福利香蕉149| 精品无码成人片一区二区| 亚洲国产精品综合久久网络| 四虎精品国产永久在线观看| 国产精品永久久久久久久久久| 久久天天躁狠狠躁夜夜av| 蜜桃av亚洲精品一区二区| 亚洲国产一区二区三区| 日本一卡2卡3卡四卡精品网站| 久久久久无码精品国产不卡| 丰满人妻熟妇乱精品视频| 开心一区二区三区激情| 亚洲色最新高清AV网站| 精品国产成人一区二区| 香蕉在线精品一区二区| 少妇被粗大的猛烈进出69影院一| 欧美亚洲h在线一区二区| 日日碰狠狠添天天爽五月婷| 久青草视频在线视频在线| 欧美日韩人成综合在线播放| 91老熟女老人国产老太| 亚洲av无码之国产精品网址蜜芽| 久久久无码精品亚洲日韩按摩| 成全我在线观看免费第二季| 老司机亚洲精品一区二区| 国产欧美日韩精品丝袜高跟鞋| 青青青爽在线视频观看| 66亚洲一卡2卡新区成片发布| 亚洲精品久久一区二区三区四区| 国产成人亚洲欧美二区综合| 亚洲av日韩av永久无码电影| 天天做天天爱夜夜爽导航| 欧美高清狂热视频60一70| 麻豆成人传媒一区二区| 九九热视频在线精品18| 亚洲av日韩av一区久久| 国产成人精品一区二三区| 色爱综合另类图片av| 国产午夜亚洲精品国产成人|