構造函數是類類型對象自動初始化的保證。它在對象創建時(定義變量、new、臨時對象、容器插入、函數返回等)由編譯器隱式調用,確保對象從一出生就處於有效狀態。C++ 提供了豐富的構造函數機制以適應各種初始化場景。
同一類可定義多個構造函數,參數列表必須不同(個數、類型、順序)。對象定義時根據實參匹配調用唯一的一個。
class Point {
int x_, y_;
public:
Point() : x_(0), y_(0) {} // 默認構造
Point(int x, int y) : x_(x), y_(y) {} // 兩參數
Point(int val) : x_(val), y_(val) {} // 單參數 (對角線點)
// Point(double x, double y) : x_(x), y_(y) {} // 錯誤: 與 (int,int) 在整型提升後可能衝突
};
Point p1; // Point()
Point p2(3, 4); // Point(int,int)
Point p3(5); // Point(int) -> (5,5)
Point p4 = 10; // 隱式轉換: Point(10) -> (10,10) 見第 4 節
Point(int) 和 Point(double) 共存時,Point(3.14) 匹配 double,Point(3) 匹配 int。Point(int, int=0) (默認參數) 與 Point(int) 衝突:Point(3) 二義性。explicit (見第 5 節)。默認參數只能在類內聲明處指定,不可在類外定義時重複。
class Config {
std::string host_;
int port_;
int timeout_;
public:
// 一個構造函數覆蓋 4 種調用方式
Config(std::string host = "localhost", int port = 8080, int timeout = 30)
: host_(std::move(host)), port_(port), timeout_(timeout) {}
};
Config c1; // 全部默認
Config c2("192.168.1.1"); // 僅指定 host
Config c3("host", 9090); // 指定 host, port
Config c4("h", 80, 60); // 全部指定
class Bad {
public:
Bad(int a = 1, int b = 1) {} // 所有參數有默認值 = 默認構造函數
Bad() {} // 錯誤: 與上行構成二義性,Bad b; 編譯失敗
};
默認構造函數 = 可無實參調用的構造函數(無參版、或所有參數有默認值)。
ClassName() 或提供全默認參數版本。std::vector<T>、std::map<K,V> 等要求 T 可默認構造 (C++11 起部分需求放寬,但仍廣泛需要)。class Widget {
int id_; // 內建類型
public:
Widget(int id) : id_(id) {} // 用戶聲明了帶參構造
// Widget() {} // 必須顯式提供,否則 Widget w; 編譯錯誤
// 或 Widget(int id = 0) : id_(id) {} 合併
};
可用單個實參調用的構造函數(含除第一參數外全有默認值的多參數版本)定義了從實參類型到類類型的隱式轉換序列。
class Data {
std::string s_;
public:
Data(const std::string& str = "") : s_(str) {} // 轉換構造函數
void set(const Data& d) { s_ = d.s_; }
};
void func(const Data& d) { /*...*/ }
Data d1("hello"); // 直接構造
Data d2 = "world"; // 隱式轉換: string -> Data (拷貝初始化)
func("temp"); // 隱式轉換: string -> Data 臨時對象 -> const Data&
d2.set("again"); // 隱式轉換: string -> Data 臨時對象 -> const Data&
func(10) 可能匹配 func(Data) 而非預期的 func(int)。Data d = 3.14; 是構造還是賦值?實為構造。explicit 關鍵字 — 禁止隱式轉換在構造函數聲明前加 explicit,禁用拷貝初始化和隱式轉換,僅允許直接初始化 (括號/花括號) 和強制轉換 (static_cast/C-style cast)。
class SafeData {
std::string s_;
public:
explicit SafeData(const std::string& str = "") : s_(str) {} // explicit
// explicit SafeData(int len) : s_(len, ' ') {} // 也可用於多參數 (C++11)
};
SafeData sd1("ok"); // ✅ 直接初始化
SafeData sd2 = "bad"; // ❌ 編譯錯誤: 拷貝初始化被禁止
// func("temp"); // ❌ 編譯錯誤: 隱式轉換被禁止
func(SafeData("temp")); // ✅ 顯式構造臨時對象
func(static_cast<SafeData>("temp")); // ✅ 強制轉換
explicit 最佳實踐「除非有極強理由需要隱式轉換,否則所有單參數構造函數(以及多參數但除第一外全有默認值的)都應聲明為
explicit。」 — C++ Core Guidelines C.46
std::string、std::vector、std::shared_ptr、std::unique_ptr、std::chrono::duration 等單參數構造均為 explicit。explicit(bool) 條件顯式,可根據模板參數動態決定。形式:ClassName(const ClassName& other) (也可非 const,但極少用)。用於:
T t2 = t1; (拷貝初始化)void func(T t); func(t1);T func() { T t; return t; } (可能觸發 RVO/NRVO 優化)vec.push_back(t1);若未聲明,編譯器生成逐成員拷貝:按順序調用每個非靜態數據成員的拷貝構造函數。適用於無資源管理的類 (POD、僅含值類型成員)。
class DeepString {
char* data_;
size_t len_;
public:
DeepString(const char* s = "") { /* 分配內存並拷貝 */ }
~DeepString() { delete[] data_; }
// 必須自定義深拷貝
DeepString(const DeepString& other) : len_(other.len_) {
data_ = new char[len_ + 1];
std::memcpy(data_, other.data_, len_ + 1);
}
// 同理需要拷貝賦值運算符 operator=
// C++11 還需移動構造/移動賦值 (Rule of Five)
};
| 場景 | 需自定義 | C++98 (Rule of Three) | C++11 (Rule of Five) |
|---|---|---|---|
| 管理動態內存/文件句柄/鎖/網絡連接等資源 | 是 | 析構、拷貝構造、拷貝賦值 | + 移動構造、移動賦值 |
| 僅含值類型/智能指針/容器 (RAII 類型) | 否 | 默認即可 (Rule of Zero) | 默認即可 (Rule of Zero) |
| 基類 (多態刪除) | 虛析構 | 虛析構 | 虛析構 + 默認/保護移動 |