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

《C++ 沉思錄》閱讀筆記:類別設計四大特殊函數深度解析

     編輯:關於C語言

前言

C++ 類別中有四個編譯器會自動生成的特殊成員函數:**建構函數、解構函數、複製建構函數、複製指派運算子**。理解它們的觸發時機、預設行為與自訂必要性,是寫出正確、高效 C++ 程式碼的關鍵。本文整理自《C++ 沉思錄》核心觀點,輔以實戰經驗。

1. 為什麼需要建構函數?

  • **簡單類別** (POD、無不變式) 可完全依賴編譯器合成預設建構函數。
  • **複雜類別** 需建立不變式、獲取資源 (記憶體、檔案、鎖)、初始化 const/reference 成員、執行基類/成員子物件建構 → **必須自訂建構函數**。
  • **資源獲取即初始化 (RAII)**:建構函數獲取資源,解構函數釋放,確保例外安全。

2. 資料成員與方法的可視性抉擇

  • public:介面契約,穩定、最小、完整。
  • private:實作細節、不變式保護、資源所有權。
  • protected:僅供衍生類別存取,**慎用**;傾向 private + protected 虛函數 (Template Method)。
  • **替代 Getter**:對 private 資料成員,可提供 const T& 唯讀存取,避免複製開銷,且保證封裝性。
    class Stick {
        const int &length;  // 唯讀參考,綁定至 true_length
        int true_length;
    public:
        Stick(int l = 1) : true_length(l), length(true_length) {}
        void setLength(int v) { true_length = v; } // length 自動同步
    };

3. 無參數建構函數的必要性

  • **陣列建立**:Stick arr[1024]; 需無參建構函數逐一初始化元素。
  • **標準容器**:vector<Stick> v(10); 同理。
  • **衍生類別**:若基類無無參建構函數,衍生類別**必須**在初始化列表顯式呼叫基類建構函數。
  • **建議**:若類別語意允許「空白/預設狀態」,**提供無參建構函數**;否則刪除 (= delete) 以防誤用。

4. 是否必須在建構函數初始化所有成員?

  • **最佳實踐**:**盡可能在初始化列表完成所有成員初始化** (含基類、const/reference 成員、無預設建構函數的成員)。
  • **例外**:若成員有合理的「未初始化」狀態 (如緩衝區指標稍後 malloc),可延遲初始化,但需建立「已初始化」旗標防止誤用。
  • **效能**:初始化列表直接建構,賦值則是「預設建構 + 指派」,後者多一次函數呼叫。

5. 類別需要解構函數嗎?

  • **需要**當:類別**擁有資源** (裸指標記憶體、檔案描述符、Socket、鎖、資料庫連線) 且該資源**不由成員物件自動管理** (如 std::unique_ptr, std::fstream 已遵循 RAII)。
  • **不需要**當:所有資源皆由 RAII 類別封裝,或類別無資源所有權 (純數值/邏輯類別)。
  • **規則**:**誰分配,誰釋放**;若建構函數 new,解構函數必須 delete。

6. 虛擬解構函數:何時需要?

只有當類別作為「多態基類」被 delete 時 才需要虛擬解構函數。

class Base { public: virtual ~Base() {} }; // 正確
class Derived : public Base { ~Derived() { cout << "~Derived\n"; } };

Base *p = new Derived;
delete p; // 呼叫 Derived::~Derived() → Base::~Base()
  • 若基類解構函數**非虛擬**,delete p 僅呼叫 Base::~Base(),**衍生類別解構函數不被調用** → 資源洩漏、行為未定義。
  • **並非所有基類都需虛擬解構函數**:若從未透過基類指標刪除衍生物件 (如私有繼承、CRTP、純實作繼承),加上虛擬函數會增加虛表指標開銷,違背「不為不用付費」原則。
  • 經驗法則 (Effective C++ 條款 7):**「類別若有任何虛擬函數,解構函數幾乎確定應為虛擬」**。反之,無虛擬函數且非多態基類 → 無需虛擬解構函數。

7. 複製建構函數:何時自訂?

編譯器合成的複製建構函數執行**成員逐位複製 (shallow copy)**。需自訂當:

  • 類別擁有**指標/資源**,淺複製會導致雙重釋放 (double free) 或資源洩漏。
  • 需實作**深複製** (分配新資源、複製內容)。
  • 需**禁止複製**:宣告 private 複製建構函數且不實作 (C++11: = delete)。
class String {
    char *data;
    String(const String&);            // 私有,禁止複製
    String& operator=(const String&); // 私有,禁止指派
public:
    // ...
};

8. 複製指派運算子:關鍵細節

  • **僅在「已建構物件」指派時觸發**。T a = b; (複製初始化) 呼叫**複製建構函數**,非指派運算子。
  • **自指派檢查**:if (this == &rhs) return *this; 防止 a = a 導致資源提前釋放。
  • **強烈例外安全**:Copy-and-swap 慣用語。
    String& operator=(String rhs) { // 以值傳遞利用複製建構函數
        swap(*this, rhs);
        return *this;
    }
  • **回傳 *this 參考**,支援鏈式指派 a = b = c。

總結:四大特殊函數決策表

函數預設行為需自訂條件C++11 現代寫法
建構函數無參、成員預設建構資源獲取、不變式建立、const/ref 成員委派建構、預設/刪除
解構函數成員逆序解構釋放非 RAII 資源虛擬/預設/刪除
複製建構淺複製深複製、禁止複製= default / = delete
複製指派淺指派深複製、禁止指派、自指派安全Copy-and-swap / = default / = delete

掌握這四大函數的生命週期、觸發條件與自訂策略,是從「會寫 C++」邁向「寫好 C++」的分水嶺。

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