前言
建構子與解構子是類別資源管理的雙引擎,構成 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 包裝器 | 釋放動作 |
| 動態記憶體 | std::unique_ptr, std::shared_ptr, std::vector | delete/delete[] |
| 檔案 | std::fstream, FILE* 自定義包裝 | fclose |
| 互斥鎖 | std::lock_guard, std::unique_lock | unlock |
| Socket | 自定義 / Boost.Asio | closesocket |
| OpenGL 物件 | 自定義包裝器 | glDelete* |
自定義 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
七、常見陷阱與除錯
| 陷阱 | 後果 | 防範 |
| 基類解構子非 virtual | 多型 delete 僅呼叫基類解構子 → 資源洩漏/UB | 基類解構子永遠 virtual |
| 建構子拋出例外 | 已建構成員/基類會正確解構,但物件不存在 | 建構子內資源用 RAII 包裝 |
| 解構子拋出例外 | 若在堆疊展開期間再拋出 → terminate() | 解構子 noexcept (C++11 預設) |
| 成員初始化順序不匹配宣告順序 | 參考/指標使用未初始化成員 | 初始化清單順序 = 宣告順序 |
| 遺漏移動建構子 | 容器重新配置/回傳值觸發昂貴複製 | Rule of Five / Rule of Zero |
結語
建構子建立不變式、獲取資源;解構子銷毀不變式、釋放資源。兩者構成 RAII,讓 C++ 成為少數能以確定性方式管理任意資源的語言。現代 C++ 透過 = default/= delete、移動語意、標準庫 RAII 型別,將資源管理從「手工藝」提升為「工程標準」。
- 上一篇文章: C++ 面向物件程式設計入門:類別、封裝與存取控制
- 下一篇文章: C++Builder 多執行緒程式設計技術深度解析:同步機制與生產者-消費者實作
Copyright ©
程式碼之家 All Rights Reserved