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

 

Item 32: 確保你的public inheritance 塑模出 "is-a"關係

如果你寫了一個 class D ("Derived") class B ("Base") 公開繼承,你就是在告訴 C++ 編譯器(以及你的代碼的讀者)每一個類型爲 D 的對象也是一個類型爲 B 的對象,但是反之則不然。你就是在說 B 描繪了一個比 D 更一般的概念,D 描述了一個比 B 更特殊的概念。你就是在聲稱一個類型爲 B 的對象可以使用的任何地方,一個類型爲 D 的對象一樣可以使用,因爲每一個類型爲 D 的對象也就是一個類型爲 B 的對象。另一方面,如果你需要一個類型爲 D 的對象,一個類型爲 B 的對象則不行:每一個 D 都是一個 B,但是反之則不然。

public inheritance 斷言,適用於 base class objects(基類對象)的每一件事——每一件事!——也適用於 derived class objects(派生類對象)。在我們考慮繼承關係的時候,必須考慮這一點。比較典型的是矩形和正方形,通常我們會直覺的認爲正方形應該從矩形繼承。但問題出來了:

考慮如下代碼:

class Rectangle {
public:
  virtual void setHeight(int newHeight);
  virtual void setWidth(int newWidth);

  virtual int height() const;               // return current values
  virtual int width() const;

  ...

};

void makeBigger(Rectangle& r)               // function to increase r's area
{
  int oldHeight = r.height();

  r.setWidth(r.width() + 10);               // add 10 to r's width

  assert(r.height() == oldHeight);          // assert that r's
}                                           // height is unchanged

很清楚,斷言應該永遠不會失敗。makeBigger 僅僅改變了 r 的寬度,它的高度始終沒有變化。

現在,考慮以下代碼,使用 public inheritance 使得 squares 可以像 rectangles 一樣進行處理:

class Square: public Rectangle {...};

Square s;

...

assert(s.width() == s.height());           // this must be true for all squares

makeBigger(s);              // by inheritance, s is-a Rectangle,
                           // so we can increase its area

assert(s.width() == s.height());           // this must still be true
                                           // for all squares

和剛纔那個一樣明顯,第二個斷言也應該永遠不會失敗。根據定義,正方形的寬度和高度是相等的。

現在,如果在square的實例上調用setWidthsetHeight,就出現問題了。

 

總結:

在確定public繼承之前,請確定適用於base class的每一件事也一定適用於derived class,因爲每一個derived class對象也都是一個base class對象。

 

Item 33: 避免覆蓋(hiding)通過繼承而來的名稱

編譯器的搜索順序:假設有以下代碼

class Base {
private:
  int x;

public:
  virtual void mf1() = 0;
  virtual void mf2();
  void mf3();

  ...
};

class Derived: public Base {
public:
  virtual void mf1();
  void mf4();

  ...
};

 

void Derived::mf4()
{

  ...
 
mf2();

  ...
}

當編譯器看到一個名爲mf2的使用,它就必須斷定它指涉什麼。它通過搜索名爲 mf2 的某物的定義的作用域來做這件事。首先它在 local 作用域中搜索(也就是 mf4 的作用域),但是它沒有找到被稱爲 mf2 的任何東西的聲明。然後它搜索它的包含作用域,也就是 class Derived 的作用域。它依然沒有找到叫做 mf2 的任何東西,所以它上移到它的上一層包含作用域,也就是 base class 的作用域。在那裏它找到了名爲 mf2 的東西,所以搜索停止。如果在 Base 中沒有 mf2,搜索還會繼續,首先是包含 Base namespace(s)(如果有的話),最後是 global 作用域。

 

因此,Derived class中的同名成員會覆蓋base class中的成員,包括變量或函數。對成員函數來說,被覆蓋與否只與函數名相關,與函數參數,函數類型(pure virtual, virtual, non-virtual)等無關。

class Base {
private:
  int x;

public:
  virtual void mf1() = 0;
 
virtual void mf1(int);

  virtual void mf2();

  void mf3();
 
void mf3(double);
  ...
};

class Derived: public Base {
public:
  virtual void mf1();
 
void mf3();
  void mf4();
  ...
};

base class 中的所有名爲 mf1 mf3 的函數被 derived class 中的名爲 mf1 mf3 的函數覆蓋。從名字搜索的觀點看,Base::mf1 Base::mf3 不再被 Derived 繼承!

Derived d;
int x;

...
d.mf1();                   // fine, calls Derived::mf1
d.mf1(x);                  // error! Derived::mf1 hides Base::mf1
d.mf2();                   // fine, calls Base::mf2

d.mf3();                   // fine, calls Derived::mf3
d.mf3(x);                  // error! Derived::mf3 hides Base::mf3

就像你看到的,即使 base derived classes 中的函數具有不同的參數類型,它也同樣適用,而且不管函數是 virtual 還是 non-virtual,它也同樣適用。覆蓋只與名字相關!

 

解決覆蓋的辦法:

1.使用using聲明式

class Derived: public Base {
public:
 
using Base::mf1;       // make all things in Base named mf1 and mf3
 
using Base::mf3;       // visible (and public) in Derived's scope

  virtual void mf1();
  void mf3();
  void mf4();
  ...
};

現在 inheritance 就可以起到預期的作用:

Derived d;
int x;

...

d.mf1();                 // still fine, still calls Derived::mf1
d.mf1(x);                //
now okay, calls Base::mf1

d.mf2();                 // still fine, still calls Base::mf2

d.mf3();                 // fine, calls Derived::mf3
d.mf3(x);                //
now okay, calls Base::mf3

這意味着如果你從一個帶有重載函數的 base class 繼承而且你只想重定義或替換它們中的一部分,你需要爲每一個你不想覆蓋的名字使用 using declaration。如果你不這樣做,一些你希望繼承下來的名字會被覆蓋。

3.使用轉交函數: (該方法通常適用於private繼承,因爲對public繼承來說,任何一件能對base class做的事情也應該可以對derived class生效!!參見條款32)

class Base {
public:
  virtual void mf1() = 0;
  virtual void mf1(int);

  ...                                    // as before
};

class Derived: private Base {
public:
 
virtual void mf1()                   // forwarding function; implicitly
 
{ Base::mf1(); }                     // inline (see Item 30)
  ...
};

...

Derived d;
int x;

d.mf1();                               // fine, calls Derived::mf1
d.mf1(x);                              // error! Base::mf1() is hidden

總結:

derived classes 中的名字覆蓋 base classes 中的名字,在 public inheritance 中,這從來不是想要的。

爲了使隱藏的名字重新可見,使用 using declarations 或者 forwarding functions(轉調函數)。

 

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