Effective C++筆記: 繼承和麪向對象設計(二)

 

Item 34: 區分 inheritance of interface(接口繼承)和 inheritance of implementation(實現繼承)

Public繼承由2部分組成: inheritance of function interfaces(函數接口的繼承)和 inheritance of function implementations(函數實現的繼承)。

 

Base class的成員函數可以聲明爲三種類型:pure virtual, impure virtual non virtual.

爲了更好地感覺這些選擇之間的不同之處,考慮一個在圖形應用程序中表示幾何圖形的 class hierarchy(類繼承體系):

class Shape {
public:
  virtual void draw() const = 0;

  virtual void error(const std::string& msg);

  int objectID() const;

  ...
};

class Rectangle: public Shape { ... };

class Ellipse: public Shape { ... };

pure virtual: pure virtual functions(純虛擬函數)的兩個最顯著的特性是它們必須被任何繼承它們的具體類重新聲明,和抽象類中一般沒有它們的定義。

聲明一個 pure virtual function(純虛擬函數)的目的是使 derived classes 只繼承一個函數接口。

爲一個 pure virtual function(純虛擬函數)提供一個定義是有可能的。也就是說,你可以爲 Shape::draw 提供一個實現,而 C++ 也不會抱怨什麼,但是調用它的唯一方法是用 class name 限定修飾這個調用:

Shape *ps = new Shape;              // error! Shape is abstract

Shape *ps1 = new Rectangle;         // fine
ps1->draw();                     // calls Rectangle::draw

Shape *ps2 = new Ellipse;           // fine
ps2->draw();                     // calls Ellipse::draw

ps1->Shape::draw();                 // calls Shape::draw

ps2->Shape::draw();                 // calls Shape::draw

爲一個純虛函數提供定義,可以實現一種機制:爲impure virtual函數提供更平常更安全的缺省實現.

impure virtual聲明一個 simple virtual function 的目的是讓 derived classes 繼承該函數接口和一個缺省實現。Derived class可以使用該缺省實現,也可以定義自己獨有的行爲。

我們可以用以下方法來強迫子類明確的表示自己是使用缺省行爲,還是定義一個新的行爲

class Airplane {
public:
  virtual void fly(const Airport& destination)
= 0;

  ...
};

void Airplane::fly(const Airport& destination)   // an implementation of
{                                           // a pure virtual function
 
default code for flying an airplane to
 
the given destination
}

class ModelA: public Airplane {
public:
  virtual void fly(const Airport& destination)
  {
Airplane::fly(destination); }

  ...

};

class ModelB: public Airplane {
public:
  virtual void fly(const Airport& destination)
  {
Airplane::fly(destination); }

  ...

};

class ModelC: public Airplane {
public:
  virtual void fly(const Airport& destination);

  ...

};

void ModelC::fly(const Airport& destination)
{
 
code for flying a ModelC airplane to the given destination
}

本質上,fly 可以被拆成兩個基本組件。它的 declaration(聲明)指定了它的 interface(接口)(這是 derived classes(派生類)必須使用的),而它的 definition(定義)指定它的缺省行爲(這是 derived classes(派生類)可以使用的,但只是在他們明確要求這一點時)。

 

non-virtual

聲明一個 non-virtual function(非虛擬函數)的目的是使派生類既繼承一個函數的接口,又繼承一個強制的實現derived class絕不該去重新定義non virtual函數。

你可以這樣考慮 Shape::objectID 的聲明,每一個 Shape object 有一個產生 object identifier(對象標識碼),而且這個 object identifier(對象標識碼)總是用同樣的方法計算出來的,這個方法是由 Shape::objectID 的定義決定的,而且 derived class(派生類)不應該試圖改變它的做法。

總結:

Inheritance of interface(接口繼承)與 inheritance of implementation(實現繼承)不同。在 public inheritance(公開繼承)下,derived classes(派生類)總是繼承 base class interfaces(基類接口)。

Pure virtual functions(純虛擬函數)指定 inheritance of interface only(僅有接口被繼承)。

Simple (impure) virtual functions(簡單虛擬函數)指定 inheritance of interface(接口繼承)加上 inheritance of a default implementation(缺省實現繼承)。

Non-virtual functions(非虛擬函數)指定 inheritance of interface(接口繼承)加上 inheritance of a mandatory implementation(強制實現繼承)。

 

Item 35: 考慮virtual functions(虛擬函數)之外的其他選擇

總結:

Virtual函數的替代方案包括NVI手法以及Strategy設計模式的多種形式。NVI手法自身是一個特殊形式的Template Method設計模式。

將技能從成員函數移到class外部函數,帶來的一個缺點是,非成員函數無法訪問classnon public成員。

tr1::fuction對象的行爲就像一般函數指針。

 

Item 36: 絕不要重定義一個 inherited non-virtual function(通過繼承得到的非虛擬函數)

class 非常重要的一條,在item 35中也有提及。

絕對不要重新定義繼承而來的non virtual函數。

 

Item 37:絕不重新定義通過繼承得到的缺省參數值

virtual functions(虛擬函數)是 dynamically bound(動態綁定),而 default parameter values(缺省參數值)是 statically bound(靜態綁定)。

一個 object(對象)的 static type(靜態類型)就是你在程序文本中聲明給它的 type(類型)。考慮這個 class hierarchy(類繼承體系):

// a class for geometric shapes
class Shape {
public:
  enum ShapeColor { Red, Green, Blue };

  // all shapes must offer a function to draw themselves
  virtual void draw(ShapeColor color = Red) const = 0;
  ...
};

class Rectangle: public Shape {
public:
  // notice the different default parameter value — bad!
  virtual void draw(ShapeColor color = Green) const;
  ...
};

class Circle: public Shape {
public:
  virtual void draw(ShapeColor color) const;
  ...
};

現在考慮這些 pointers(指針):

Shape *ps;                       // static type = Shape*
Shape *pc = new Circle;          // static type = Shape*
Shape *pr = new Rectangle;       // static type = Shape*

dynamic types(動態類型),就像它的名字所暗示的,能在程序運行中變化,特別是通過 assignments(賦值):

ps = pc;                       // ps's dynamic type is
                               // now Circle*

ps = pr;                       // ps's dynamic type is
                               // now Rectangle*

virtual functions(虛擬函數)是 dynamically bound(動態綁定),意味着被調用的特定函數取決於被用來調用它的那個 object(對象)的 dynamic type(動態類型):

pc->draw(Shape::Red);             // calls Circle::draw(Shape::Red)

pr->draw(Shape::Red);             // calls Rectangle::draw(Shape::Red)

但是,當你考慮 virtual functions with default parameter values(帶有缺省參數值的虛擬函數)時,就全亂了套,因爲,如上所述,virtual functions(虛擬函數)是 dynamically bound(動態綁定),但 default parameters(缺省參數)是 statically bound(靜態綁定)。這就意味着你最終調用了一個定義在 derived class(派生類)中的 virtual function(虛擬函數)卻使用了一個來自 base class(基類)的 default parameter value(缺省參數值)。

pr->draw();                // calls Rectangle::draw(Shape::Red)!

在此情況下,pr dynamic type(動態類型)是 Rectangle*,所以正像你所希望的,Rectangle virtual function(虛擬函數)被調用。在 Rectangle::draw 中,default parameter value(缺省參數值)是 Green。然而,因爲 pr static type(靜態類型)是 Shape*這個函數調用的 default parameter value(缺省參數值)是從 Shape class 中取得的,而不是 Rectangle class

爲什麼 C++ 要堅持按照這種不正常的方式動作?答案是爲了運行時效率。如果 default parameter values(缺省參數值)是 dynamically bound(動態綁定),compilers(編譯器)就必須提供一種方法在運行時確定 virtual functions(虛擬函數)的 parameters(參數)的 default value(s)(缺省值),這比目前在編譯期確定它們的機制更慢而且更復雜。

 

總結:

絕不要重定義一個 inherited default parameter value(通過繼承得到的缺省參數值),因爲 default parameter value(缺省參數值)是 statically bound(靜態綁定),而 virtual functions ——你唯一應該覆寫的東西—— dynamically bound(動態綁定)。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章