国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發(fā)設計 > 正文

C++箴言:考慮可選的虛擬函數(shù)的替代方法

2019-11-17 05:09:04
字體:
供稿:網(wǎng)友

  現(xiàn)在你工作在一個視頻游戲上,你在游戲中為角色設計了一個 hierarchy(繼續(xù)體系)。你的游戲中有著變化多端的惡劣環(huán)境,角色被傷害或者其它的健康狀態(tài)降低的情況并不罕見。因此你決定提供一個 member function(成員函數(shù))healthValue,它返回一個象征角色健康狀況如何的整數(shù)。因為不同的角色計算健康值的方法可能不同,將 healthValue 聲明為 virtual(虛擬)似乎是顯而易見的設計選擇:

class GameCharacter {
public:
  virtual int healthValue() const;        // return character's health rating;
  ...                                     // derived classes may redefine this
};
  healthValue 沒有被聲明為 pure virtual(純虛)的事實暗示這里有一個計算健康值的缺省算法。

  這確實是一個顯而易見的設計選擇,而在某種意義上,這是它的缺點。因為這樣的設計過于顯而易見,你可能不會對它的其它可選方法給予足夠的關注。為了幫助你脫離 object-oriented design(面向?qū)ο笤O計)的習慣性道路,我們來考慮一些處理這個問題的其它方法。

  經(jīng)由非虛擬接口慣用法實現(xiàn)的模板方法模式

  我們以一個主張 virtual functions(虛擬函數(shù))應該幾乎總是為 PRivate(私有的)的有趣觀點開始。這一觀點的擁護者提出:一個較好的設計應該保留作為 public member function(公有成員函數(shù))的 healthValue,但應將它改為 non-virtual(非虛擬的)并讓它調(diào)用一個 private virtual function(私有虛擬函數(shù))來做真正的工作,也就是說,doHealthValue:

class GameCharacter {
public:
  int healthValue() const               // derived classes do not redefine
  {                                     // this - see Item 36

    ...                                 // do "before" stuff - see below

    int retVal = doHealthValue();       // do the real work

    ...                                 // do "after" stuff - see below

    return retVal;
  }
  ...

private:
  virtual int doHealthValue() const     // derived classes may redefine this
  {
    ...                                 // default algorithm for calculating
  }                                     // character's health
};
  在這個代碼(以及本文的其它代碼)中,我在類定義中展示 member functions(成員函數(shù))的本體。這會將它們隱式聲明為 inline(內(nèi)聯(lián))。我用這種方法展示代碼僅僅是這樣更易于看到它在做些什么。我所描述的設計與是否 inline 化無關,所以不必深究 member functions(成員函數(shù))定義在類的內(nèi)部有什么意味深長的含義。根本沒有。

  這個基本的設計——讓客戶通過 public non-virtual member functions(公有非虛擬成員函數(shù))調(diào)用 private virtual functions(私有虛擬函數(shù))——被稱為 non-virtual interface (NVI) idiom(非虛擬接口慣用法)。這是一個更通用的被稱為 Template Method(一個模式,很不幸,與 C++ templates(模板)無關)的 design pattern(設計模式)的非凡形式。我將那個 non-virtual function(非虛擬函數(shù))(例如,healthValue)稱為 virtual function's wrapper(虛擬函數(shù)的外殼)。

  NVI idiom(慣用法)的一個優(yōu)勢通過 "do 'before' stuff" 和 "do 'after' stuff" 兩個注釋在代碼中標示出來。這些注釋標出的代碼片斷在做真正的工作的 virtual function(虛擬函數(shù))之前或之后調(diào)用。這就意味著那個 wrapper(外殼)可以確保在 virtual function(虛擬函數(shù))被調(diào)用前,特定的背景環(huán)境被設置,而在調(diào)用結(jié)束之后,這些背景環(huán)境被清理。例如,"before" stuff 可以包括鎖閉一個 mutex(互斥體),生成一條日志條目,校驗類變量和函數(shù)的 preconditions(前提條件)是否被滿足,等等。"after" stuff 可以包括解鎖一個 mutex(互斥體),校驗函數(shù)的 postconditions(結(jié)束條件),類不變量的恢復,等等。假如你讓客戶直接調(diào)用 virtual functions(虛擬函數(shù)),確實沒有好的方法能夠做到這些。

  涉及 derived classes(派生類)重定義 private virtual functions(私有虛擬函數(shù))(這些重定義函數(shù)它們不能調(diào)用!)的 NVI idiom 可能會攪亂你的頭腦。這里沒有設計上的矛盾。重定義一個 virtual function(虛擬函數(shù))指定如何做某些事。調(diào)用一個 virtual function(虛擬函數(shù))指定什么時候去做。互相之間沒有關系。NVI idiom 答應 derived classes(派生類)重定義一個 virtual function(虛擬函數(shù)),這樣就給了它們控制功能如何實現(xiàn)的能力,但是 base class(基類)保留了決定函數(shù)何時被調(diào)用的權(quán)利。乍一看很希奇,但是 C++ 規(guī)定 derived classes(派生類)可以重定義 private inherited virtual functions(私有的通過繼續(xù)得到的函數(shù))是非常明智的。

  在 NVI idiom 之下,virtual functions(虛擬函數(shù))成為 private(私有的)并不是絕對必需的。在一些 class hierarchies(類繼續(xù)體系)中,一個 virtual function(虛擬函數(shù))的 derived class(派生類)實現(xiàn)被期望調(diào)用其 base class(基類)的對應物,而為了這樣的調(diào)用能夠合法,虛擬必須成為 protected(保護的),而非 private(私有的)。有時一個 virtual function(虛擬函數(shù))甚至必須是 public(公有的)(例如,polymorphic base classes(多態(tài)基類)中的 destrUCtors(析構(gòu)函數(shù))),但這樣一來 NVI idiom 就不能被真正應用。

  經(jīng)由函數(shù)指針實現(xiàn)的策略模式

  NVI idiom 是 public virtual functions(公有虛擬函數(shù))的有趣的可選替代物,但從設計的觀點來看,它比裝點門也多不了多少東西。究竟,我們還是在用 virtual functions(虛擬函數(shù))來計算每一個角色的健康值。一個更引人注目的設計主張認為計算一個角色的健康值不依靠于角色的類型——這樣的計算根本不需要成為角色的一部分。例如,我們可能需要為每一個角色的 constructor(構(gòu)造函數(shù))傳遞一個指向健康值計算函數(shù)的指針,而我們可以調(diào)用這個函數(shù)進行實際的計算:


class GameCharacter;                               // forward declaration

// function for the default health calculation algorithm
int defaultHealthCalc(const GameCharacter& gc);

class GameCharacter {
public:
  typedef int (*HealthCalcFunc)(const GameCharacter&);

  eXPlicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc)
  : healthFunc(hcf)
  {}

  int healthValue() const
  { return healthFunc(*this); }

  ...

private:
  HealthCalcFunc healthFunc;
};
  這個方法是另一個通用 design pattern(設計模式)—— Strategy 的簡單應用,相對于基于 GameCharacter hierarchy(繼續(xù)體系)中的 virtual functions(虛擬函數(shù))的方法,它提供了某些更引人注目的機動性:
  • 相同角色類型的不同實例可以有不同的健康值計算函數(shù)。例如:
class EvilBadGuy: public GameCharacter {
public:
  explicit EvilBadGuy(HealthCalcFunc hcf = defaultHealthCalc)
  : GameCharacter(hcf)
  { ... }

  ...

};
int loseHealthQuickly(const GameCharacter&);    // health calculation
int loseHealthSlowly(const GameCharacter&);     // funcs with different
                                                // behavior

EvilBadGuy ebg1(loseHealthQuickly);             // same-type charac-
EvilBadGuy ebg2(loseHealthSlowly);              // ters with different
                                                // health-related
                                                // behavior
  • 對于一個指定的角色健康值的計算函數(shù)可以在運行時改變。例如,GameCharacter 可以提供一個 member function(成員函數(shù))setHealthCalculator,它被答應代替當前的健康值計算函數(shù)。
  在另一方面,健康值計算函數(shù)不再是 GameCharacter hierarchy(繼續(xù)體系)的一個 member function(成員函數(shù))的事實,意味著它不再擁有訪問它所計算的那個對象內(nèi)部構(gòu)件的特權(quán)。例如,defaultHealthCalc 不能訪問 EvilBadGuy 的 non-public(非公有)構(gòu)件。假如一個角色的健康值計算能夠完全基于通過角色的 public interface(公有接口)可以得到的信息,這就沒什么問題,但是,假如準確的健康值計算需要 non-public(非公有)信息,就會有問題。實際上,在任何一個你要用 class(類)外部的等價機能(例如,經(jīng)由一個 non-member non-friend function(非成員非友元函數(shù))或經(jīng)由另一個 class(類)的 non-friend member function(非友元成員函數(shù)))代替 class(類)內(nèi)部的機能(例如,經(jīng)由一個 member function(成員函數(shù)))的時候,它都是一個潛在的問題。這個問題將持續(xù)影響本 Item 的剩余部分,因為所有我們要考慮的其它設計選擇都包括 GameCharacter hierarchy(繼續(xù)體系)的外部函數(shù)的使用。

  作為一個通用規(guī)則,解決對“non-member functions(非成員函數(shù))對類的 non-public(非公有)構(gòu)件的訪問的需要”的唯一方法就是削弱類的 encapsulation(封裝性)。例如,class(類)可以將 non-member functions(非成員函數(shù))聲明為 friends(友元),或者,它可以提供對“在其它情況下它更希望保持隱藏的本身的實現(xiàn)部分”的 public accessor functions(公有訪問者函數(shù))。使用一個 function pointer(函數(shù)指針)代替一個 virtual function(虛擬函數(shù))的優(yōu)勢(例如,具有逐對象健康值計算函數(shù)的能力和在運行時改變這樣的函數(shù)的能力)是否能抵消可能的降低 GameCharacter 的 encapsulation(封裝性)的需要是你必須在設計時就做出決定的重要部分。 更多文章 更多內(nèi)容請看C/C++進階技術(shù)文檔專題,或   經(jīng)由 tr1::function 實現(xiàn)的策略模式


  一旦你習慣了 templates(模板)和 implicit interfaces(隱式接口)的應用,function-pointer-based(基于函數(shù)指針)的方法看上去就有些死板了。健康值的計算為什么必須是一個 function(函數(shù)),而不能是某種簡單的行為類似 function(函數(shù))的東西(例如,一個 function object(函數(shù)對象))?假如它必須是一個 function(函數(shù)),為什么不能是一個 member function(成員函數(shù))?為什么它必須返回一個 int,而不是某種能夠轉(zhuǎn)型為 int 的類型?

  假如我們用一個 tr1::function 類型的對象代替一個 function pointer(函數(shù)指針)(諸如 healthFunc),這些約束就會消失。這樣的對象可以持有 any callable entity(任何可調(diào)用實體)(例如,function pointer(函數(shù)指針),function object(函數(shù)對象),或 member function pointer(成員函數(shù)指針)),這些實體的標志性特征就是兼容于它所期待的東西。我們馬上就會看到這樣的設計,這次使用了 tr1::function:

class GameCharacter;                                 // as before
int defaultHealthCalc(const GameCharacter& gc);      // as before

class GameCharacter {
public:
   // HealthCalcFunc is any callable entity that can be called with
   // anything compatible with a GameCharacter and that returns anything
   // compatible with an int; see below for details
   typedef std::tr1::function<int (const GameCharacter&)> HealthCalcFunc;
   explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc)
   : healthFunc(hcf)
   {}

   int healthValue() const
   { return healthFunc(*this);   }

   ...

private:
  HealthCalcFunc healthFunc;
};
  就像你看到的,HealthCalcFunc 是一個 tr1::function instantiation(實例化)的 typedef。這意味著它的行為類似一個普通的 function pointer(函數(shù)指針)類型。我們近距離看看 HealthCalcFunc 究竟是一個什么東西的 typedef:

std::tr1::function<int (const GameCharacter&)>
  這里我突出了這個 tr1::function instantiation(實例化)的“target signature(目標識別特征)”。這個 target signature(目標識別特征)是“取得一個引向 const GameCharacter 的 reference(引用),并返回一個 int 的函數(shù)”。這個 tr1::function 類型的(例如,HealthCalcFunc 類型的)對象可以持有兼容于這個 target signature(目標識別特征)的 any callable entity(任何可調(diào)用實體)。兼容意味著這個實體的參數(shù)能夠隱式地轉(zhuǎn)型為一個 const GameCharacter&,而它的返回類型能夠隱式地轉(zhuǎn)型為一個 int。

  與我們看到的最近一個設計(在那里 GameCharacter 持有一個指向一個函數(shù)的指針)相比,這個設計幾乎相同。僅有的區(qū)別是目前的 GameCharacter 持有一個 tr1::function 對象——指向一個函數(shù)的 generalized(泛型化)指針。除了達到“clients(客戶)在指定健康值計算函數(shù)時有更大的靈活性”的效果之外,這個變化是如此之小,以至于我寧愿對它視而不見:

short calcHealth(const GameCharacter&);          // health calculation
                                                 // function; note
                                                 // non-int return type

struct HealthCalculator {                        // class for health
  int Operator()(const GameCharacter&) const     // calculation function
  { ... }                                        // objects
};

class GameLevel {
public:
  float health(const GameCharacter&) const;      // health calculation
  ...                                            // mem function; note
};                                               // non-int return type


class EvilBadGuy: public GameCharacter {         // as before
  ...
};
class EyeCandyCharacter:   public GameCharacter {  // another character
  ...                                              // type; assume same
};                                                 // constructor as
                                                   // EvilBadGuy


EvilBadGuy ebg1(calcHealth);                       // character using a
                                                   // health calculation
                                                   // function


EyeCandyCharacter ecc1(HealthCalculator());        // character using a
                                                   // health calculation
                                                   // function object

GameLevel currentLevel;
...
EvilBadGuy ebg2(                                   // character using a
  std::tr1::bind(&GameLevel::health,               // health calculation
          currentLevel,                            // member function;
          _1)                                      // see below for details
);
  就個人感覺而言:我發(fā)現(xiàn) tr1::function 能讓你做的事情是如此讓人驚喜,它令我渾身興奮異常。假如你沒有感到興奮,那可能是因為你正目不轉(zhuǎn)睛地盯著 ebg2 的定義并對 tr1::bind 的調(diào)用會發(fā)生什么迷惑不解。請耐心地聽我解釋。

  比方說我們要計算 ebg2 的健康等級,應該使用 GameLevel class(類)中的 health member function(成員函數(shù))。現(xiàn)在,GameLevel::health 是一個被聲明為取得一個參數(shù)(一個引向 GameCharacter 的引用)的函數(shù),但是它實際上取得了兩個參數(shù),因為它同時得到一個隱式的 GameLevel 參數(shù)——指向 this。然而,GameCharacters 的健康值計算函數(shù)只取得單一的參數(shù):將被計算健康值的 GameCharacter。假如我們要使用 GameLevel::health 計算 ebg2 的健康值,我們必須以某種方式“改造”它,以使它適應只取得唯一的參數(shù)(一個 GameCharacter),而不是兩個(一個 GameCharacter 和一個 GameLevel)。在本例中,我們總是要使用 currentLevel 作為 GameLevel 對象來計算 ebg2 的健康值,所以每次調(diào)用 GameLevel::health 計算 ebg2 的健康值時,我們就要 "bind"(凝固)currentLevel 來作為 GameLevel 的對象來使用。這就是 tr1::bind 的調(diào)用所做的事情:它指定 ebg2 的健康值計算函數(shù)應該總是使用 currentLevel 作為 GameLevel 對象。

  我們跳過一大堆的細節(jié),諸如為什么 "_1" 意味著“當為了 ebg2 調(diào)用 GameLevel::health 時使用 currentLevel 作為 GameLevel 對象”。這樣的細節(jié)并沒有什么啟發(fā)性,而且它們將轉(zhuǎn)移我所關注的基本點:在計算一個角色的健康值時,通過使用 tr1::function 代替一個 function pointer(函數(shù)指針),我們將答應客戶使用 any compatible callable entity(任何兼容的可調(diào)用實體)。很酷是不是?

  “經(jīng)典的”策略模式

  假如你比 C++ 更加深入地進入 design patterns(設計模式),一個 Strategy 的更加習以為常的做法是將 health-calculation function(健康值計算函數(shù))做成一個獨立的 health-calculation hierarchy(健康值計算繼續(xù)體系)的 virtual member function(虛擬成員函數(shù))。做成的 hierarchy(繼續(xù)體系)設計看起來就像這樣:


C++箴言:考慮可選的虛擬函數(shù)的替代方法
  假如你不熟悉 UML 記法,這不過是在表示當把 EvilBadGuy 和 EyeCandyCharacter 作為 derived classes(派生類)時,GameCharacter 是這個 inheritance hierarchy(繼續(xù)體系)的根;HealthCalcFunc 是另一個帶有 derived classes(派生類)SlowHealthLoser 和 FastHealthLoser 的 inheritance hierarchy(繼續(xù)體系)的根;而每一個 GameCharacter 類型的對象包含一個指向“從 HealthCalcFunc 派生的對象”的指針。

  這就是相應的框架代碼:

class GameCharacter;                            // forward declaration

class HealthCalcFunc {
public:

  ...
  virtual int calc(const GameCharacter& gc) const
  { ... }
  ...

};

HealthCalcFunc defaultHealthCalc;

class GameCharacter {
public:
  explicit GameCharacter(HealthCalcFunc *phcf = &defaultHealthCalc)
  : pHealthCalc(phcf)
  {}

  int healthValue() const
  { return pHealthCalc->calc(*this);}

  ...

private:
  HealthCalcFunc *pHealthCalc;
};
  這個方法的吸引力在于對于熟悉“標準的”Strategy pattern(策略模式)實現(xiàn)的人可以很快地識別出來,再加上它提供了通過在 HealthCalcFunc hierarchy(繼續(xù)體系)中增加一個 derived class(派生類)而微調(diào)已存在的健康值計算算法的可能性。

  小結(jié)

  本文的基本建議是當你為嘗試解決的問題尋求一個設計時,你應該考慮可選的 virtual functions(虛擬函數(shù))的替代方法。以下是對我們考察過的可選方法的一個簡略的回顧:
  • 使用 non-virtual interface idiom (NVI idiom)(非虛擬接口慣用法),這是用 public non-virtual member functions(公有非虛擬成員函數(shù))包裝可訪問權(quán)限較小的 virtual functions(虛擬函數(shù))的 Template Method design pattern(模板方法模式)的一種形式。
  • function pointer data members(函數(shù)指針數(shù)據(jù)成員)代替 virtual functions(虛擬函數(shù)),一種 Strategy design pattern(策略模式)的顯而易見的形式。
  • tr1::function data members(數(shù)據(jù)成員)代替 virtual functions(虛擬函數(shù)),這樣就答應使用兼容于你所需要的東西的 any callable entity(任何可調(diào)用實體)。這也是 Strategy design pattern(策略模式)的一種形式。
  • virtual functions in another hierarchy(另外一個繼續(xù)體系中的虛擬函數(shù))代替 virtual functions in one hierarchy(單獨一個繼續(xù)體系中的虛擬函數(shù))。這是 Strategy design pattern(策略模式)的習以為常的實現(xiàn)。
  這不是一個可選的 virtual functions(虛擬函數(shù))的替代設計的詳盡無遺的列表,但是它足以使你確信這些是可選的方法。此外,它們之間互為比較的優(yōu)劣應該使你考慮它們時更為明確。

  為了避免陷入 object-oriented design(面向?qū)ο笤O計)的習慣性道路,時不時地給車輪一些有益的顛簸。有很多其它的道路。值得花一些時間去考慮它們。

  Things to Remember
  • 可選的 virtual functions(虛擬函數(shù))的替代方法包括 NVI 慣用法和 Strategy design pattern(策略模式)的各種變化形式。NVI 慣用法本身是 Template Method design pattern(模板方法模式)的一個實例。
  • 將一個機能從一個 member function(成員函數(shù))中移到 class(類)之外的某個函數(shù)中的一個危害是 non-member function(非成員函數(shù))沒有訪問類的 non-public members(非公有成員)的途徑。
  • tr1::function 對象的行為類似 generalized function pointers(泛型化的函數(shù)指針)。這樣的對象支持所有兼容于一個給定的目標特征的 callable entities(可調(diào)用實體)。
更多文章 更多內(nèi)容請看C/C++進階技術(shù)文檔專題,或

發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 虞城县| 合阳县| 万宁市| 乌兰浩特市| 丰顺县| 长兴县| 句容市| 珠海市| 兰坪| 乌拉特前旗| 肃宁县| 黔江区| 广汉市| 宁安市| 灵宝市| 小金县| 攀枝花市| 金阳县| 高要市| 潜江市| 黔西| 剑阁县| 玛曲县| 辉县市| 萍乡市| 永善县| 延寿县| 韶山市| 灵武市| 鸡泽县| 永泰县| 东明县| 民县| 福鼎市| 德清县| 江安县| 若羌县| 永安市| 兴化市| 黄骅市| 宁国市|