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;
}
关键原则总结
- 内置类型:必须手工初始化
- 类成员:
- 始终使用成员初始化列表
- 初始化列表顺序与成员声明顺序一致
- 静态对象:
- 避免跨编译单元的初始化依赖
- 使用"局部静态对象"模式解决初始化顺序问题
重要提醒:
- 构造函数体内赋值 ≠ 初始化(效率低且不适用于所有类型)
- 未初始化内置类型会导致随机值(安全漏洞常见来源)
- 静态对象初始化顺序问题可通过"首次调用时初始化"模式解决