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外部函數,帶來的一個缺點是,非成員函數無法訪問class的non 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(動態綁定)。