首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> C++ 入門

C++ 存取控制完全指南:private/protected/public、繼承存取調整、友元與最佳實踐

     編輯:C++ 入門

前言:封裝的基石與 C++ 獨特機制

存取控制是 C++ 封裝機制的核心,決定「誰可以存取什麼」。C++ 提供三種基本存取指定符、三種繼承方式組合產生的存取調整規則、以及打破封裝邊界的友元機制。本文系統梳理完整規則體系,並給出現代 C++ 封裝設計最佳實踐。

一、基本存取指定符:三層防線

指定符類別內部衍生類別友元函式/類別外部代碼 (物件/指標/參考)
public✅✅✅✅
protected✅✅✅❌
private (預設)✅❌✅❌

關鍵點:

  • 類別內部無限制:私有/保護/公有成員在類別成員函式中均可自由存取
  • 友元打破封裝:friend 函式/類別獲得與成員函式同等存取權
  • 物件存取僅限 public:obj.public_member 合法,obj.private_member 編譯錯誤

二、繼承方式與存取調整矩陣

繼承方式決定基類成員在衍生類中的呈現存取層級:

基類成員原層級public 繼承protected 繼承private 繼承
publicpublicprotectedprivate
protectedprotectedprotectedprivate
private不可存取 (但存在於物件中)不可存取不可存取

語義解讀

  • public 繼承 = "is-a" 關係:介面繼承,基類公有介面保持公有 — 95%+ 場景首選
  • protected 繼承 = "implemented-in-terms-of" (受保護介面):基類介面僅對衍生類體系可見
  • private 繼承 = "implemented-in-terms-of" (完全隱藏):基類介面僅供衍生類內部實現使用,外部不可見 — 優先考慮組合 (composition)

程式碼驗證範例


class Base {
public:    int pub = 1;
protected: int pro = 2;
private:   int pri = 3;
};

class PubDerived : public Base {
    void test() { pub; pro; /* pri; // 錯誤 */ }
};
class ProDerived : protected Base {
    void test() { pub; pro; /* pri; // 錯誤 */ } // pub/pro 變為 protected
};
class PriDerived : private Base {
    void test() { pub; pro; /* pri; // 錯誤 */ } // pub/pro 變為 private
};

int main() {
    PubDerived d1; d1.pub; /* d1.pro; d1.pri; // 錯誤 */
    ProDerived d2; /* d2.pub; d2.pro; d2.pri; // 全部錯誤 */
    PriDerived d3; /* d3.pub; d3.pro; d3.pri; // 全部錯誤 */
}

三、友元機制:受控的封裝突破

兩種友元形式


class Matrix {
    std::vector data_;
    int rows_, cols_;
public:
    // 友元函式:非成員函式獲得私有存取
    friend Matrix multiply(const Matrix& a, const Matrix& b);
    
    // 友元類別:整個類別獲得私有存取
    friend class MatrixFactory;
    
    // C++11 樣板友元:特定實例化獲得存取
    template friend class Serializer;
};

// 友元函式定義 (非成員)
Matrix multiply(const Matrix& a, const Matrix& b) {
    // 直接存取 a.data_, a.rows_ 等私有成員
    return Matrix(/* ... */);
}

友元最佳實踐

  • 極度克制使用:每個 friend 都是封裝洞,增加耦合與維護成本
  • 正當場景:
    • 運算子多載 (如 operator<<, operator==) 需存取私有資料
    • 工廠/建造者模式 (friend class Factory)
    • 序列化/反序列化框架 (friend class Serializer)
    • 單元測試存取私有狀態 (條件編譯 #ifdef TEST)
  • 替代方案:公有 getter/setter、protected 介面、Pimpl 惯用法隱藏私有細節

四、存取控制的兩種維度:水平 vs 垂直

維度定義可存取成員
水平存取外部代碼透過物件/指標/參考存取成員僅 public
垂直存取衍生類別透過繼承存取基類成員public + protected

本質:存取控制作用於「名稱查找」與「存取檢查」階段,不影響物件記憶體佈局。私有成員依然存在於衍生類物件中,只是名稱在衍生類作用域不可見。

五、現代 C++ 封裝設計最佳實踐

1. 資料成員預設 private,行為介面 public


class BankAccount {
    // 實現細節完全隱藏
    int64_t balance_cents_ = 0;
    std::mutex mtx_;
    std::vector history_;
    
public:
    // 穩定介面
    explicit BankAccount(int64_t initial = 0) : balance_cents_(initial) {}
    
    // 命令:修改狀態
    bool deposit(int64_t amount) { /* ... */ }
    bool withdraw(int64_t amount) { /* ... */ }
    
    // 查詢:不修改狀態
    int64_t balance() const noexcept { 
        std::lock_guard lock(mtx_); 
        return balance_cents_; 
    }
    
    // 只讀存取歷史 (避免拷貝大向量)
    const std::vector& history() const noexcept { return history_; }
};

2. 繼承介面與實現分離


// 介面類別:純虛擬解構函式 + 純虛擬介面
class IRepository {
public:
    virtual ~IRepository() = default;
    virtual std::optional find_by_id(ID id) = 0;
    virtual void save(const Entity& e) = 0;
};

// 實現類別:final 封鎖繼承 (除非設計為可擴展)
class SqlRepository final : public IRepository {
    // 私有繼承內部實現細節 (如連線池、快取)
    std::unique_ptr pool_;
    std::unique_ptr cache_;
    
public:
    // 實現介面...
};

3. Pimpl 惯用法:編譯防火牆與二進位相容


// Widget.hpp (公開標頭檔,極小依賴)
class Widget {
    struct Impl; // 前向宣告
    std::unique_ptr pimpl_;
public:
    Widget();
    ~Widget(); // 必須在 .cpp 定義 (Impl 完整型別可見)
    Widget(Widget&&) noexcept;
    Widget& operator=(Widget&&) noexcept;
    void do_work();
};

// Widget.cpp (實現細節,變更不觸發使用者重編)
#include "Widget.hpp"
#include "HeavyInternal.hpp"
#include 

struct Widget::Impl {
    HeavyInternal engine_;
    std::vector cache_;
};

Widget::Widget() : pimpl_(std::make_unique()) {}
Widget::~Widget() = default;
// 移動操作預設即可
void Widget::do_work() { pimpl_->engine_.process(pimpl_->cache_); }

4. 避免「getter/setter 爆炸」—— 行為導向介面


// ❌ 貧血模型:資料結構偽裝成類別
struct User {
    std::string name;
    std::string email;
    int age;
    // 一堆 get/set...
};

// ✅ 豐富模型:封裝業務規則
class User {
    std::string name_;
    std::string email_;
    int age_;
public:
    User(std::string name, std::string email, int age) 
        : name_(std::move(name)), email_(std::move(email)), age_(age) {
        validate();
    }
    
    void change_email(std::string new_email) {
        if (!is_valid_email(new_email)) throw InvalidEmail{};
        email_ = std::move(new_email);
    }
    
    void celebrate_birthday() { ++age_; }
    
    // 僅暴露必要查詢
    const std::string& name() const noexcept { return name_; }
    bool is_adult() const noexcept { return age_ >= 18; }
};

六、常見面試陷阱與誤區

誤區/陷阱正確理解
「private 成員不被繼承」私有成員存在於衍生類物件中,只是名稱不可見;大小/佈局受影響
「protected 允許衍生類物件存取基類 protected 成員」衍生類成員函式可存取 this 指向物件的 protected 成員,不能存取其他基類物件的 protected 成員
「friend 破壞封裝,絕不使用」運算子多載、工廠、序列化等場景適度使用 反而增強封裝 (避免暴露 getter/setter)
「繼承方式影響物件大小」繼承方式僅影響名稱查找與存取檢查,不影響記憶體佈局、虛函式表、大小
「struct 預設 public,class 預設 private,其他無差異」完全正確。唯一語義差異。慣例:struct 用於純資料聚合 (POD-like),class 用於不變量封裝

總結

C++ 存取控制系統雖規則繁多,核心邏輯清晰:

  1. 基本三層:private (類別內+友元) < protected (+衍生類) < public (全域)
  2. 繼承調整:public 繼承保持語義、protected/private 繼承遞減可見度
  3. 友元補充:特定非成員操作需私有存取時的受控突破
  4. 現代實踐:資料私有化、介面行為化、Pimpl 隔離實現、組合優於私有繼承

掌握這套體系,不僅能通過面試,更能設計出封裝嚴密、介面穩定、易於維護、二進位相容的 C++ 類別架構——這才是存取控制的工程價值。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved