沒有學不會的C++:public, protected 和 private 關鍵字

C++ 中的繼承有 3 種方式,分別是 public、protected 和 private,這三種方式分別對應不同的父類成員的訪問權限,總結如下:

  1. public、protected 和 private 子類都不能訪問父類的 private 成員
  2. public 作用域下,父類的 public 成員會被繼承為 public,父類的 protected 成員會被繼承為 protected
  3. protected 作用域下,父類的 public 和 protected 成員會被繼承為 protected 成員
  4. 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 個規則:

  1. public, protected 和 private 子類都不能訪問父類的 private 成員
  2. public 作用域下,父類的 public 成員會被繼承為 public,父類的 protected 成員會被繼承為 protected
  3. protected 作用域下,父類的 public 和 protected 成員會被繼承為 protected 成員
  4. private 作用域下,父類的 public 和 protected 成員會被繼承為 private 成員

以及 2 個使用場景:

  1. public 繼承是一種 is-a 的關系
  2. private 或 protected 繼承是 has-a 關系;但我們一般不使用這種方式實現 has-a 需求,一般會使用組合模式

參考:Advanced C++: Inheritance - Public, Protected, and Private

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容