明輝手游網(wǎng)中心:是一個免費提供流行視頻軟件教程、在線學(xué)習(xí)分享的學(xué)習(xí)平臺!

C++箴言:謹慎使用私有繼承

[摘要]在《C++箴言:確保公開繼承模擬“is-a”》一文中論述了 C++ 將 public inheritance(公有繼承)視為一個 is-a 關(guān)系。當給定一個 hierarchy(繼承體系),其中有一個 class Student 從一個 class Person 公有繼承,當為一個函數(shù)調(diào)用的成功而...
在《C++箴言:確保公開繼承模擬“is-a”》一文中論述了 C++ 將 public inheritance(公有繼承)視為一個 is-a 關(guān)系。當給定一個 hierarchy(繼承體系),其中有一個 class Student 從一個 class Person 公有繼承,當為一個函數(shù)調(diào)用的成功而有必要時,需要將 Students 隱式轉(zhuǎn)型為 Persons,它通過向編譯器展示來做到這一點。用 private inheritance(私有繼承)代替 public inheritance(公有繼承)把這個例子的一部分重做一下是值得的:

  class Person { ... };
  class Student: private Person { ... }; // inheritance is now private

  void eat(const Person p); // anyone can eat

  void study(const Student s); // only students study

  Person p; // p is a Person
  Student s; // s is a Student

  eat(p); // fine, p is a Person

  eat(s); // error! a Student isn't a Person

  很明顯,private inheritance(私有繼承)不意味著 is-a。那么它意味著什么呢?

  “喂!”你說:“在我們得到它的含義之前,我們先看看它的行為。private inheritance(私有繼承)有怎樣的行為呢?”好吧,支配 private inheritance(私有繼承)的第一個規(guī)則你只能從動作中看到:與 public inheritance(公有繼承)對照,如果 classes(類)之間的 inheritance relationship(繼承關(guān)系)是 private(私有)的,編譯器通常不會將一個 derived class object(派生類對象)(諸如 Student)轉(zhuǎn)型為一個 base class object(基類對象)(諸如 Person)。這就是為什么為 object(對象)s 調(diào)用 eat 會失敗。第二個規(guī)則是從一個 private base class(私有基類)繼承的 members(成員)會成為 derived class(派生類)的 private members(私有成員),即使它們在 base class(基類)中是 protected(保護)的或 public(公有)的。

  行為不過如此。這就給我們帶來了含義。private inheritance(私有繼承)意味著 is-implemented-in-terms-of(是根據(jù)……實現(xiàn)的)。如果你使 class(類)D 從 class(類)B 私有繼承,你這樣做是因為你對于利用在 class(類)B 中才可用的某些特性感興趣,而不是因為在 types(類型)B 和 types(類型)D 的 objects(對象)之間有什么概念上的關(guān)系。同樣地,private inheritance(私有繼承)純粹是一種實現(xiàn)技術(shù)。(這也就是為什么你從一個 private base class(私有基類)繼承的每一件東西都在你的 class(類)中變成 private(私有)的原因:它全部都是實現(xiàn)的細節(jié)。)利用《接口繼承和實現(xiàn)繼承》中提出的條款,private inheritance(私有繼承)意味著只有 implementation(實現(xiàn))應(yīng)該被繼承;interface(接口)應(yīng)該被忽略。

  如果 D 從 B 私有繼承,它就意味著 D objects are implemented in terms of B objects(D 對象是根據(jù) B 對象實現(xiàn)的),沒有更多了。private inheritance(私有繼承)在 software design(軟件設(shè)計)期間沒有任何意義,只在 software implementation(軟件實現(xiàn))期間才有。 private inheritance(私有繼承)意味著 is-implemented-in-terms-of(是根據(jù)……實現(xiàn)的)的事實有一點混亂,正如《通過composition模擬“has-a”》一文中所指出的 composition(復(fù)合)也有同樣的含義。你怎么預(yù)先在它們之間做出選擇呢?答案很簡單:只要你能就用 composition(復(fù)合),只有在絕對必要的時候才用 private inheritance(私有繼承)。什么時候是絕對必要呢?主要是當 protected members(保護成員)和/或 virtual functions(虛擬函數(shù))摻和進來的時候,另外還有一種與空間相關(guān)的極端情況會使天平向 private inheritance(私有繼承)傾斜。我們稍后再來操心這種極端情況。

  畢竟,它只是一種極端情況。 假設(shè)我們工作在一個包含 Widgets 的應(yīng)用程序上,而且我們認為我們需要更好地理解 Widgets 是怎樣被使用的。例如,我們不僅要知道 Widget member functions(成員函數(shù))被調(diào)用的頻度,還要知道 call ratios(調(diào)用率)隨著時間的流逝如何變化。帶有清晰的執(zhí)行階段的程序在不同的執(zhí)行階段可以有不同的行為側(cè)重。例如,一個編譯器在解析階段對函數(shù)的使用與優(yōu)化和代碼生成階段就有很大的不同。

  我們決定修改 Widget class 以持續(xù)跟蹤每一個 member function(成員函數(shù))被調(diào)用了多少次。在運行時,我們可以周期性地檢查這一信息,與每一個 Widget 的這個值相伴的可能還有我們覺得有用的其它數(shù)據(jù)。為了進行這項工作,我們需要設(shè)立某種類型的 timer(計時器),以便在到達收集用法統(tǒng)計的時間時我們可以知道。

  盡可能復(fù)用已有代碼,而不是寫新的代碼,我在我的工具包中翻箱倒柜,而且滿意地找到下面這個 class(類):

  class Timer {
  public:
  explicit Timer(int tickFrequency);
  virtual void onTick() const; // automatically called for each tick
  ...
  };

  這正是我們要找的:一個我們能夠根據(jù)我們的需要設(shè)定 tick 頻率的 Timer object,而在每次 tick 時,它調(diào)用一個 virtual function(虛擬函數(shù))。我們可以重定義這個 virtual function(虛擬函數(shù))以便讓它檢查 Widget 所在的當前狀態(tài)。很完美!

  在《C++箴言:確保公開繼承模擬“is-a”》一文中論述了 C++ 將 public inheritance(公有繼承)視為一個 is-a 關(guān)系。當給定一個 hierarchy(繼承體系),其中有一個 class Student 從一個 class Person 公有繼承,當為一個函數(shù)調(diào)用的成功而有必要時,需要將 Students 隱式轉(zhuǎn)型為 Persons,它通過向編譯器展示來做到這一點。用 private inheritance(私有繼承)代替 public inheritance(公有繼承)把這個例子的一部分重做一下是值得的:

  class Person { ... };
  class Student: private Person { ... }; // inheritance is now private

  void eat(const Person p); // anyone can eat

  void study(const Student s); // only students study

  Person p; // p is a Person
  Student s; // s is a Student

  eat(p); // fine, p is a Person

  eat(s); // error! a Student isn't a Person

  很明顯,private inheritance(私有繼承)不意味著 is-a。那么它意味著什么呢?

  “喂!”你說:“在我們得到它的含義之前,我們先看看它的行為。private inheritance(私有繼承)有怎樣的行為呢?”好吧,支配 private inheritance(私有繼承)的第一個規(guī)則你只能從動作中看到:與 public inheritance(公有繼承)對照,如果 classes(類)之間的 inheritance relationship(繼承關(guān)系)是 private(私有)的,編譯器通常不會將一個 derived class object(派生類對象)(諸如 Student)轉(zhuǎn)型為一個 base class object(基類對象)(諸如 Person)。這就是為什么為 object(對象)s 調(diào)用 eat 會失敗。第二個規(guī)則是從一個 private base class(私有基類)繼承的 members(成員)會成為 derived class(派生類)的 private members(私有成員),即使它們在 base class(基類)中是 protected(保護)的或 public(公有)的。

  行為不過如此。這就給我們帶來了含義。private inheritance(私有繼承)意味著 is-implemented-in-terms-of(是根據(jù)……實現(xiàn)的)。如果你使 class(類)D 從 class(類)B 私有繼承,你這樣做是因為你對于利用在 class(類)B 中才可用的某些特性感興趣,而不是因為在 types(類型)B 和 types(類型)D 的 objects(對象)之間有什么概念上的關(guān)系。同樣地,private inheritance(私有繼承)純粹是一種實現(xiàn)技術(shù)。(這也就是為什么你從一個 private base class(私有基類)繼承的每一件東西都在你的 class(類)中變成 private(私有)的原因:它全部都是實現(xiàn)的細節(jié)。)利用《接口繼承和實現(xiàn)繼承》中提出的條款,private inheritance(私有繼承)意味著只有 implementation(實現(xiàn))應(yīng)該被繼承;interface(接口)應(yīng)該被忽略。

  如果 D 從 B 私有繼承,它就意味著 D objects are implemented in terms of B objects(D 對象是根據(jù) B 對象實現(xiàn)的),沒有更多了。private inheritance(私有繼承)在 software design(軟件設(shè)計)期間沒有任何意義,只在 software implementation(軟件實現(xiàn))期間才有。 private inheritance(私有繼承)意味著 is-implemented-in-terms-of(是根據(jù)……實現(xiàn)的)的事實有一點混亂,正如《通過composition模擬“has-a”》一文中所指出的 composition(復(fù)合)也有同樣的含義。你怎么預(yù)先在它們之間做出選擇呢?答案很簡單:只要你能就用 composition(復(fù)合),只有在絕對必要的時候才用 private inheritance(私有繼承)。什么時候是絕對必要呢?主要是當 protected members(保護成員)和/或 virtual functions(虛擬函數(shù))摻和進來的時候,另外還有一種與空間相關(guān)的極端情況會使天平向 private inheritance(私有繼承)傾斜。我們稍后再來操心這種極端情況。

  畢竟,它只是一種極端情況。 假設(shè)我們工作在一個包含 Widgets 的應(yīng)用程序上,而且我們認為我們需要更好地理解 Widgets 是怎樣被使用的。例如,我們不僅要知道 Widget member functions(成員函數(shù))被調(diào)用的頻度,還要知道 call ratios(調(diào)用率)隨著時間的流逝如何變化。帶有清晰的執(zhí)行階段的程序在不同的執(zhí)行階段可以有不同的行為側(cè)重。例如,一個編譯器在解析階段對函數(shù)的使用與優(yōu)化和代碼生成階段就有很大的不同。

  我們決定修改 Widget class 以持續(xù)跟蹤每一個 member function(成員函數(shù))被調(diào)用了多少次。在運行時,我們可以周期性地檢查這一信息,與每一個 Widget 的這個值相伴的可能還有我們覺得有用的其它數(shù)據(jù)。為了進行這項工作,我們需要設(shè)立某種類型的 timer(計時器),以便在到達收集用法統(tǒng)計的時間時我們可以知道。

  盡可能復(fù)用已有代碼,而不是寫新的代碼,我在我的工具包中翻箱倒柜,而且滿意地找到下面這個 class(類):

  class Timer {
  public:
  explicit Timer(int tickFrequency);
  virtual void onTick() const; // automatically called for each tick
  ...
  };

  這正是我們要找的:一個我們能夠根據(jù)我們的需要設(shè)定 tick 頻率的 Timer object,而在每次 tick 時,它調(diào)用一個 virtual function(虛擬函數(shù))。我們可以重定義這個 virtual function(虛擬函數(shù))以便讓它檢查 Widget 所在的當前狀態(tài)。很完美!