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

C++ 面向物件核心:建構子與解構子完整指南 (RAII 基石)

     編輯:VC 教學

前言

建構子與解構子是類別資源管理的雙引擎,構成 RAII (Resource Acquisition Is Initialization) 機制的核心。掌握其規則、多載、呼叫順序與現代演進,是寫出無洩漏、例外安全程式碼的關鍵。

一、建構子基本規則

規則說明
名稱與類別相同無回傳型別 (連 void 也不能寫)
可多載依參數區分:預設、參數化、複製、移動
每個類別至少有一個未宣告則編譯器合成預設建構子 (無參、無操作)
一旦宣告任何建構子編譯器不再合成預設建構子
靜態/全域物件位元模式歸零 (隱式零初始化)
區域/動態物件未初始化則值不定 (隨機垃圾值)

二、建構子分類與語法

1. 預設建構子

class Widget {
    int id;
public:
    Widget() : id(0) {} // 明確初始化
    // 或 Widget() = default; (C++11) 讓編譯器合成
};

2. 參數化建構子

class Student {
    std::string name;
    int age;
public:
    Student(const std::string& n, int a) : name(n), age(a) {}
    // 成員初始化清單:效率最高、必須用於 const/參考成員
};

3. 複製/移動建構子 (Rule of Five)

class Resource {
    int* data;
public:
    Resource(const Resource& other) : data(new int(*other.data)) {} // 深複製
    Resource(Resource&& other) noexcept : data(other.data) { other.data = nullptr; } // 移動
};

4. 委派建構子 (C++11)

class Date {
    int y, m, d;
public:
    Date(int y_, int m_, int d_) : y(y_), m(m_), d(d_) { Validate(); }
    Date() : Date(1970, 1, 1) {} // 委派給主建構子
    Date(int y_, int m_) : Date(y_, m_, 1) {}
};

5. 繼承建構子 (C++11)

class Base {
public:
    Base(int x) : val(x) {}
protected:
    int val;
};
class Derived : public Base {
public:
    using Base::Base; // 繼承所有 Base 建構子
    // Derived(int x) : Base(x) {} 自動可用
};

6. explicit 建構子 (防隱式轉換)

class BigInt {
public:
    explicit BigInt(int v) : value(v) {} // BigInt b = 10; 錯誤
    // BigInt b(10); BigInt b{10}; 合法
private:
    int value;
};

三、解構子

class FileHandle {
    FILE* fp;
public:
    FileHandle(const char* path) : fp(fopen(path, "r")) {}
    ~FileHandle() { if (fp) fclose(fp); } // 單一、無參、不可多載
    // C++11: ~FileHandle() = default; (若無資源需手動釋放)
};
  • 名稱:~ClassName
  • 無參數、無回傳值、不可多載
  • 自動呼叫時機:區域物件離開作用域、delete 動態物件、陣列銷毀 (逆序)、標準容器清除元素
  • 基類解構子應宣告為 virtual (多型刪除安全)

四、物件生命週期與呼叫順序

class Base { public: Base() { print("Base()"); } ~Base() { print("~Base()"); } };
class Member { public: Member() { print("Member()"); } ~Member() { print("~Member()"); } };
class Derived : public Base {
    Member m;
public:
    Derived() { print("Derived()"); }
    ~Derived() { print("~Derived()"); }
};

// 建構順序:
// 1. Base()      (基類)
// 2. Member()    (成員,依宣告順序)
// 3. Derived()   (衍生類)

// 解構順序 (完全相反):
// 1. ~Derived()
// 2. ~Member()
// 3. ~Base()

五、RAII: 資源獲取即初始化

核心原則:資源 (記憶體、檔案、鎖、Socket、GPU 緩衝區) 的獲取在建構子,釋放在解構子。物件生命週期綁定資源生命週期。

標準庫 RAII 範例

自定義 RAII 範例

class ScopedLock {
    std::mutex& mtx;
public:
    explicit ScopedLock(std::mutex& m) : mtx(m) { mtx.lock(); }
    ~ScopedLock() { mtx.unlock(); }
    // 禁止複製/移動
    ScopedLock(const ScopedLock&) = delete;
    ScopedLock& operator=(const ScopedLock&) = delete;
};

// 使用
void SafeFunc() {
    static std::mutex mtx;
    ScopedLock lock(mtx); // 進入自動鎖定
    // 關鍵區...
} // 離開作用域自動解鎖,即使拋出例外

六、現代 C++ 關鍵演進

  • C++11:移動建構子/指派、= default/= delete、委派建構子、繼承建構子、explicit 擴充
  • C++17:保證複製省略 (RVO/NRVO 強制)、std::optional/std::variant RAII 型別
  • C++20:概念限制建構子、std::source_location 除錯資訊
  • C++23:推導這個 (deducing this) 簡化 CRTP

七、常見陷阱與除錯

資源RAII 包裝器釋放動作
動態記憶體std::unique_ptr, std::shared_ptr, std::vectordelete/delete[]
檔案std::fstream, FILE* 自定義包裝fclose
互斥鎖std::lock_guard, std::unique_lockunlock
Socket自定義 / Boost.Asioclosesocket
OpenGL 物件自定義包裝器glDelete*
陷阱後果防範
基類解構子非 virtual多型 delete 僅呼叫基類解構子 → 資源洩漏/UB基類解構子永遠 virtual
建構子拋出例外已建構成員/基類會正確解構,但物件不存在建構子內資源用 RAII 包裝
解構子拋出例外若在堆疊展開期間再拋出 → terminate()解構子 noexcept (C++11 預設)
成員初始化順序不匹配宣告順序參考/指標使用未初始化成員初始化清單順序 = 宣告順序
遺漏移動建構子容器重新配置/回傳值觸發昂貴複製Rule of Five / Rule of Zero

結語

建構子建立不變式、獲取資源;解構子銷毀不變式、釋放資源。兩者構成 RAII,讓 C++ 成為少數能以確定性方式管理任意資源的語言。現代 C++ 透過 = default/= delete、移動語意、標準庫 RAII 型別,將資源管理從「手工藝」提升為「工程標準」。

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