構造函數是類的「出生證明」,其設計直接決定對象一生的效能、安全與可維護性。本文從**初始化效率**、**資源洩漏防禦**、**異常傳播**三大維度,定義「完美構造函數」標準並給出可落地的代碼模板。
// ❌ 錯誤:賦值而非初始化
class Bad {
std::string name;
ExpensiveObject obj;
public:
Bad(const char* n) { name = n; obj = ExpensiveObject(); }
// name: 默認構造 → 析構 → 賦值構造 → 析構 (2次構造+1次析構)
// obj: 默認構造 → 賦值構造 → 析構
};
// ✅ 正確:初始化列表直接構造
class Good {
std::string name;
ExpensiveObject obj;
public:
Good(const char* n) : name(n), obj() {} // 各成員僅構造一次
};
鐵律:所有成員、基類**必須**在初始化列表中顯式初始化(除非有極強理由不初始化)。
參考 *Effective C++* 條款 10:構造函數中每一個資源獲取動作,必須由 RAII 物件托管。
class ResourceManager {
std::unique_ptr<A> a_;
std::unique_ptr<B> b_;
std::vector<char> buffer_;
std::fstream log_;
public:
ResourceManager()
: a_(std::make_unique<A>()),
b_(std::make_unique<B>()),
buffer_(1024 * 1024),
log_("app.log", std::ios::out) {
if (!log_) throw std::runtime_error("log open failed");
// 任一步驟拋異常,已構造的智慧指針/容器/流自動清理
}
// 無需析構函數,Rule of Zero
};
class Service {
std::unique_ptr<Config> cfg_;
std::unique_ptr<DbConn> db_;
public:
Service() {
try {
cfg_ = std::make_unique<Config>("config.ini");
db_ = std::make_unique<DbConn>(cfg_->dbUrl());
} catch (...) {
// 智慧指針已自動清理 cfg_/db_
throw; // 重拋,讓上層決策:重試/降級/崩潰
}
}
};
原則:構造函數**不返回錯誤碼**,失敗即拋異常;捕獲僅為清理非 RAII 資源(極罕見),清理後**必須重拋**。
class Perfect {
// 1. 成員按聲明順序排列(初始化順序 = 聲明順序)
const std::string id_; // const 成員必須列表初始化
std::unique_ptr<Resource> res_; // RAII 管理動態資源
std::vector<Task> tasks_; // 容器自動管理
mutable std::mutex mtx_; // 同步原語直接作為成員
public:
// 2. 參數按邏輯分組,避免長參數列表
struct Config {
std::string id;
size_t reserveTasks = 0;
std::string resourcePath;
};
// 3. 顯式構造函數,支援移動語義
explicit Perfect(Config cfg)
: id_(std::move(cfg.id)), // 移動構造,零拷貝
res_(std::make_unique<Resource>(cfg.resourcePath)),
tasks_() {
tasks_.reserve(cfg.reserveTasks); // 體積預留,避免擴容
// 不在這裡做複雜 I/O / 網路 / 鎖操作
// 若必須:放入單獨 init() 方法,構造後調用
}
// 4. 禁用拷貝,支援移動 (Rule of Five)
Perfect(const Perfect&) = delete;
Perfect& operator=(const Perfect&) = delete;
Perfect(Perfect&&) = default;
Perfect& operator=(Perfect&&) = default;
// 5. 非虛介面 (NVI) 模式:公共非虛 + 保護虛
void process() { doProcess(); } // 可加前置/後置契約檢查
protected:
virtual void doProcess() = 0;
};
| # | 檢查項 | 標準 |
|---|---|---|
| 1 | 所有成員/基類是否在初始化列表? | 是 |
| 2 | 初始化列表順序是否與成員聲明順序一致? | 是 (避免使用未初始化成員) |
| 3 | 是否有裸 `new`/`malloc`/`fopen` 等非 RAII 獲取? | 無 (全智慧指針/容器/封裝) |
| 4 | 構造失敗是否拋異常而非返回錯誤碼? | 是 |
| 5 | 捕獲異常後是否重拋? | 是 (或終止程序) |
| 6 | const/reference 成員是否初始化? | 是 |
| 7 | 是否避免在構造函數中調用虛函數? | 是 (虛表未就緒) |
| 8 | 是否提供 `explicit` 防止隱式轉換? | 單參數構造函數建議 |
| 9 | 是否遵循 Rule of Zero / Five? | 優先 Zero (全 RAII) |
| 10 | 參數是否按語義分組為 Config 結構體? | 參數 > 4 時建議 |