Effective C++ 條款4:確定對象被使用前已先被初始化
核心思想:永遠在使用對象前將其初始化。未初始化對象是未定義行為的常見來源,尤其對于內置類型。
1. 內置類型手動初始化
int x = 0; // 手動初始化
const char* text = "Hello"; // 指針初始化
double d; // ? 危險!未初始化(值隨機)
2. 類成員使用初始化列表
原因:
- 避免先默認構造再賦值的性能開銷
const
成員和引用成員必須用初始化列表
class PhoneNumber { /*...*/ };class ABEntry {
public:ABEntry(const std::string& name, const std::string& address, const std::list<PhoneNumber>& phones);
private:std::string theName;std::string theAddress;std::list<PhoneNumber> thePhones;int numTimesConsulted;
};// ? 正確:初始化列表(高效且安全)
ABEntry::ABEntry(const std::string& name, const std::string& address, const std::list<PhoneNumber>& phones)
: theName(name), // 調用string拷貝構造函數theAddress(address), // 同上thePhones(phones), // 調用list拷貝構造函數numTimesConsulted(0) // 內置類型顯式初始化
{} // 構造函數體為空// ? 錯誤:構造函數內賦值(效率低)
ABEntry::ABEntry(...)
{ theName = name; // 先默認構造再賦值theAddress = address; // 額外開銷!thePhones = phones;numTimesConsulted = 0; // 此時才"初始化"
}
3. 初始化順序規則
- 成員初始化順序只取決于聲明順序(與初始化列表順序無關)
- 始終按成員聲明順序寫初始化列表
class C {int a;int b;
public:// ? 危險:實際初始化順序 a(b) -> b(val)(a使用未初始化的b)C(int val) : b(val), a(b) {} // ? 正確:聲明順序=初始化順序C(int val) : a(val), b(a) {}
};
4. 靜態對象初始化問題(跨編譯單元)
問題:不同編譯單元的全局靜態對象初始化順序不確定
// FileSystem.cpp
class FileSystem { public: int numDisks() const; };
extern FileSystem tfs; // 全局對象// Directory.cpp
class Directory {
public:Directory() {int disks = tfs.numDisks(); // ? tfs可能尚未初始化}
};
Directory tempDir; // 依賴tfs的全局對象
解決方案:使用局部靜態對象(Singleton模式)
// 用函數代替直接訪問全局對象
FileSystem& tfs() {static FileSystem fs; // 首次調用時初始化return fs;
}Directory& tempDir() {static Directory td; // 首次調用時初始化(此時tfs()已可用)return td;
}
關鍵原則總結
- 內置類型:必須手工初始化
- 類成員:
- 始終使用成員初始化列表
- 初始化列表順序與成員聲明順序一致
- 靜態對象:
- 避免跨編譯單元的初始化依賴
- 使用"局部靜態對象"模式解決初始化順序問題
重要提醒:
- 構造函數體內賦值 ≠ 初始化(效率低且不適用于所有類型)
- 未初始化內置類型會導致隨機值(安全漏洞常見來源)
- 靜態對象初始化順序問題可通過"首次調用時初始化"模式解決