C++ 中的繼承有 3 種方式,分別是 public、protected 和 private,這三種方式分別對應不同的父類成員的訪問權限,總結如下:
- public、protected 和 private 子類都不能訪問父類的 private 成員
- public 作用域下,父類的 public 成員會被繼承為 public,父類的 protected 成員會被繼承為 protected
- protected 作用域下,父類的 public 和 protected 成員會被繼承為 protected 成員
- private 作用域下,父類的 public 和 protected 成員會被繼承為 private 成員
這 4 條規則實際上只有第 2 條是常用的,下面說下這 3 種作用域的使用場景。
public 繼承是一種 is-a 關系
我們經常會將子對象強制轉換(casting)為父對象,而 public 繼承在這種強制轉換的場景下是無障礙的,這種情況下,子類對象可以理解為一種特殊的父類對象,即它們是一種 is-a 的關系;除此之外,其他的由 protected 或 private 作用域繼承而來的對象就不具備這樣的關系,下面是一個簡單的例子:
#include <iostream>
using namespace std;
class B {
private:
int val_;
public:
B(int val) : val_(val) {}
void print_val() { cout << "val_ = " << val_ << endl; }
};
class D_pub : public B {
public:
D_pub(int val)
: B(val)
{}
};
class D_pro : protected B {
public:
D_pro(int val)
: B(val)
{}
};
int main() {
D_pub pub(1);
B* b = &pub;
b->print_val();
D_pro dpro(1);
B* b2 = &dpro; // error: 'B' is an inaccessible base of 'D_pro'
}
上面例子中,類 B 是一個基類,D_pub 是一個使用 public 作用域的子類,而 D_pro 是使用 protected 作用域的子類,我們在 main 中分別創建 D_pub 的對象 pub 和 D_pro 的對象 dpro,并分別賦值給父類指針,可以看到,將 D_pro 對象賦值給父類指針的語句報編譯錯誤,原因在于類 B 中的可訪問成員在 D_pro 中變成了不可訪問成員,即 D_pro 對象不再是一個特殊意義的 B 對象,它們之間不具備 is-a 關系。以此類推,private 繼承的子類和父類也沒有 is-a 關系。
protected 和 private 繼承是一種 has-a 關系
protected 和 private 繼承類似于組合模式(composition),它是一種 has-a 關系,我們看一個組合模式的例子:
class hat {
public:
void wear() {}
};
class child {
hat h_;
public:
void hat_wear() { h_.wear(); }
};
上面的代碼中,child 類是以將 hat 組合進來的方式實現的,即讓 child 類也具備 hat 的方法,一種很好的辦法是將 hat 作為 child 的一個成員,從語義上,child 和 hat 具備 has-a 的關系。下面我們看使用 protected 或 private 繼承如何實現 has-a 的關系:
class child : private hat {
public:
using hat::wear; // 此時 child 對象就可以調用 hat::wear 方法了
};
int main() {
child c;
c.wear();
}
我們將 child 以 private 的方式繼承自 hat,并將 hat::wear
方法放置在 child 的 public 作用域中,這樣 child 就「擁有了 hat 的能力」,它們之間也是一種 has-a 關系。
雖然不同的實現達到了相同的效果,但依然不建議使用 private 或 protected 的方式實現 has-a 的關系,而建議更多的使用組合模式,一是因為組合模式更為直觀,其二是因為組合模式將組合的多個對象解耦(它們沒有多一層繼承關系),其三是組合模式更為靈活,試想一個類有多個組合對象的情況。
總結
以上,我們介紹了 C++ 中繼承的三種作用域,此時我們需要記住 4 個規則:
- public, protected 和 private 子類都不能訪問父類的 private 成員
- public 作用域下,父類的 public 成員會被繼承為 public,父類的 protected 成員會被繼承為 protected
- protected 作用域下,父類的 public 和 protected 成員會被繼承為 protected 成員
- private 作用域下,父類的 public 和 protected 成員會被繼承為 private 成員
以及 2 個使用場景:
- public 繼承是一種 is-a 的關系
- private 或 protected 繼承是 has-a 關系;但我們一般不使用這種方式實現 has-a 需求,一般會使用組合模式
參考:Advanced C++: Inheritance - Public, Protected, and Private