[翻譯] Effective C++, 3rd Edition, Item 35: 考慮可選的 virtual functions(虛擬函數)的替代方法(下)

來源:互聯網
上載者:User

(點擊此處,接上篇)

The Strategy Pattern via tr1::function(經由 tr1::function 實現的策略模式)

一旦你習慣了 templates(模板)和 implicit interfaces(隱式介面)(參見 Item 41)的應用,function-pointer-based(基於函數指標)的方法看上去就有些死板了。健康值的計算為什麼必須是一個 function(函數),而不能是某種簡單的行為類似 function(函數)的東西(例如,一個 function object(函數對象))?如果它必須是一個 function(函數),為什麼不能是一個 member function(成員函數)?為什麼它必須返回一個 int,而不是某種能夠轉型為 int 的類型?

如果我們用一個 tr1::function 類型的對象代替一個 function pointer(函數指標)(諸如 healthFunc),這些約束就會消失。就像 Item 54 中的解釋,這樣的對象可以持有 any callable entity(任何可調用實體)(例如,function pointer(函數指標),function object(函數對象),或 member function pointer(成員函數指標)),這些實體的標誌性特徵就是相容於它所期待的東西。我們馬上就會看到這樣的設計,這次使用了 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(函數指標)類型。我們近距離看看 HealthCalcFunc 究竟是一個什麼東西的 typedef:

std::tr1::function<int (const GameCharacter&)>

這裡我突出了這個 tr1::function instantiation(執行個體化)的“target signature(目標識別特徵)”。這個 target signature(目標識別特徵)是“取得一個引向 const GameCharacter 的 reference(引用),並返回一個 int 的函數”。這個 tr1::function 類型的(例如,HealthCalcFunc 類型的)對象可以持有相容於這個 target signature(目標識別特徵)的 any callable entity(任何可調用實體)。相容意味著這個實體的參數能夠隱式地轉型為一個 const GameCharacter&,而它的傳回型別能夠隱式地轉型為一個 int。

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

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
);

就個人感覺而言:我發現 tr1::function 能讓你做的事情是如此讓人驚喜,它令我渾身興奮異常。如果你沒有感到興奮,那可能是因為你正目不轉睛地盯著 ebg2 的定義並對 tr1::bind 的調用會發生什麼迷惑不解。請耐心地聽我解釋。

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

我們跳過一大堆的細節,諸如為什麼 "_1" 意味著“當為了 ebg2 調用 GameLevel::health 時使用 currentLevel 作為 GameLevel 對象”。這樣的細節並沒有什麼啟發性,而且它們將轉移我所關注的基本點:在計算一個角色的健康值時,通過使用 tr1::function 代替一個 function pointer(函數指標),我們將允許客戶使用 any compatible callable entity(任何相容的可調用實體)。很酷是不是?

The "Classic" Strategy Pattern(“經典的”策略模式)

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

如果你不熟悉 UML 記法,這不過是在表示當把 EvilBadGuy 和 EyeCandyCharacter 作為 derived classes(衍生類別)時,GameCharacter 是這個 inheritance hierarchy(繼承體系)的根;HealthCalcFunc 是另一個帶有 derived classes(衍生類別)SlowHealthLoser 和 FastHealthLoser 的 inheritance hierarchy(繼承體系)的根;而每一個 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(策略模式)實現的人可以很快地識別出來,再加上它提供了通過在 HealthCalcFunc hierarchy(繼承體系)中增加一個 derived class(衍生類別)而微調已存在的健康值計算演算法的可能性。

Summary(概要)

這個 Item 的基本建議是當你為嘗試解決的問題尋求一個設計時,你應該考慮可選的 virtual functions(虛擬函數)的替代方法。以下是對我們考察過的可選方法的一個簡略的回顧:

  • 使用 non-virtual interface idiom (NVI idiom)(非虛擬介面慣用法),這是用 public non-virtual member functions(公有非虛擬成員函數)封裝可存取權限較小的 virtual functions(虛擬函數)的 Template Method design pattern(模板方法模式)的一種形式。
  • function pointer data members(函數指標資料成員)代替 virtual functions(虛擬函數),一種 Strategy design pattern(策略模式)的顯而易見的形式。
  • tr1::function data members(資料成員)代替 virtual functions(虛擬函數),這樣就允許使用相容於你所需要的東西的 any callable entity(任何可調用實體)。這也是 Strategy design pattern(策略模式)的一種形式。
  • virtual functions in another hierarchy(另外一個繼承體系中的虛擬函數)代替 virtual functions in one hierarchy(單獨一個繼承體系中的虛擬函數)。這是 Strategy design pattern(策略模式)的習以為常的實現。

這不是一個可選的 virtual functions(虛擬函數)的替代設計的詳盡無遺的列表,但是它足以使你確信這些是可選的方法。此外,它們之間互為比較的優劣應該使你考慮它們時更為明確。

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

Things to Remember

  • 可選的 virtual functions(虛擬函數)的替代方法包括 NVI 慣用法和 Strategy design pattern(策略模式)的各種變化形式。NVI 慣用法本身是 Template Method design pattern(模板方法模式)的一個執行個體。
  • 將一個機能從一個 member function(成員函數)中移到 class(類)之外的某個函數中的一個危害是 non-member function(非成員函數)沒有訪問類的 non-public members(非公有成員)的途徑。
  • tr1::function 對象的行為類似 generalized function pointers(泛型化的函數指標)。這樣的對象支援所有相容於一個給定的目標特徵的 callable entities(可調用實體)。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.