Effective C++ 条款 04:确定对象被使用前已先被初始化
条款 04:确定对象被使用前已先被初始化
目的
确保每个构造函数都能将对象的所有成员初始化,并且使用 成员初值列(Member Initialization List)来完成初始化,而不是在构造函数本体中进行赋值。成员初值列是在构造函数开始时执行的,它确保了成员变量在构造函数体内部使用之前已经被初始化。
关键点
- 成员初值列:成员初值列是在构造函数的声明部分使用冒号(
:
)初始化类成员的语法。在构造函数的主体执行之前,成员变量会根据初值列进行初始化。初值列的使用比构造函数体内的赋值语句更加高效。 - 避免赋值:在构造函数体内使用赋值语句给成员变量赋值不是推荐的做法。应该优先使用成员初值列初始化成员变量。
示例
class ABEntry {
public:
ABEntry(const std::string& name, const std::string& address, const std::list<PhoneNumber>& phones)
: theName(name), // 初始化 theName
theAddress(address), // 初始化 theAddress
thePhones(phones), // 初始化 thePhones
numTimesConsulted(0) // 初始化 numTimesConsulted
{
// 这里不再进行成员变量的赋值,避免重复操作
}
private:
std::string theName;
std::string theAddress;
std::list<PhoneNumber> thePhones;
int numTimesConsulted;
};
上面代码中,theName
、theAddress
、thePhones
和 numTimesConsulted
都在成员初值列中初始化,而构造函数体内没有再进行赋值操作,这样能确保每个成员都在对象创建时得到正确的初始化。
手工初始化内置类型对象
C++ 不保证内置类型(如 int
、double
等)会自动初始化,所以建议手动初始化这些成员变量。确保内置类型在使用之前已经赋予一个合理的值。
成员初值列的顺序
在成员初值列中,成员变量应该按照它们在类中声明的顺序来初始化。这是因为 C++ 对象的成员变量初始化顺序是根据它们在类中声明的顺序,而不是在成员初值列中的顺序。
class MyClass {
public:
MyClass(int a, int b) : x(a), y(b) {} // 按照声明顺序初始化 x 和 y
private:
int x;
int y;
};
这段代码确保了 x
和 y
的初始化顺序与它们在类中声明的顺序一致,避免了潜在的问题。
跨编译单元的初始化次序问题
为了避免跨编译单元之间的初始化顺序问题,建议使用 局部静态对象(local static object)来代替 非局部静态对象(non-local static object)。局部静态对象会在第一次调用时进行初始化,因此它们能够在多线程环境下保证线程安全。
示例
class FileSystem {
// FileSystem 类的定义
};
FileSystem& tfs() {
static FileSystem fs; // 局部静态对象,在首次调用时初始化
return fs;
}
class Directory {
public:
Directory(/* params */) {
std::size_t disks = tfs().numDisks(); // 使用局部静态对象 tfs()
}
};
在这段代码中,FileSystem
类的局部静态对象 fs
会在首次调用 tfs()
时被初始化,确保初始化顺序的正确性。
总结
- 使用 成员初值列 初始化对象的成员变量,避免在构造函数体内进行赋值。
- 确保内置类型的成员变量手动初始化,避免未定义行为。
- 初始化顺序应与成员变量在类中的声明顺序一致。
- 使用 局部静态对象 替代 非局部静态对象,避免跨编译单元的初始化顺序问题。