1. 构造函数最好使用成员初值列,而不要在构造函数本体内使用赋值操作。
2. 初值列列出的成员变量,其排列次序应该和它们在 class 中的声明次序相同。
3. 为免除“跨编译单元之初始化次序”问题,请以 local static 对象替换 non-local static 对象。
对象内的成员通常采用构造函数进行初始化,但要注意赋值(assignment)和初始化(initialization)的区别!!!
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;
theAddress = address;
thePhones = phones;
numTimesConsulted = 0;
}
上面虽然会让 ABEntry 对象带有你期望的值,但不是最佳做法。C++规定,对象的成员变量的初始化动作发生在进入构造函数本体之前。在 ABEntry 构造函数内,theName,theAddress 和 thePhones 都不是被初始化,而是被赋值。初始化的发生时间更早,发生于这些成员的 default 构造函数被自动调用之时(比进入 ABEntry 构造函数本体的时间更早)。
因此,ABEntry 构造函数的更佳写法是使用成员初值列替换赋值动作:
ABEntry::ABEntry(const std::string& name, const std::string& address,
const std::list<PhoneNumber>& phones)
:theName(name), // 现在,这些都是初始化
theAddress(address),
thePhones(phones),
numTimesConSulted(0)
{} // 现在,构造函数本体不进行任何动作
改进后的构造函数和改进前的最终结果相同,但效率更高。因为,在改进后,theName 以 name 为初值进行拷贝构造;而改进前,先调用默认构造函数,然后再进行拷贝赋值操作。因此,只调用一次拷贝构造函数效率更高。
注意: 如果成员变量是 const 或 references,它们就一定需要初值,不能被赋值(见条款5)。因此,最简单的做法是:总是使用成员初值列!!!
跨编译单元之初始化次序
编译单元: 指产出单一目标文件(single object file)的那些源码。基本上它是单一源码文件加上其所含入的头文件(#include files)。
问题: 如果某编译单元内的某个 non-local static 对象的初始化动作使用了另一编译单元内的某个 non-local static 对象,它所用到的这个对象可能尚未被初始化,因为 C++ 对“定义于不同编译单元内的 non-local static 对象”的初始化次序并无明确定义,因此程序会出问题。例如:
class FileSystem {
public:
...
std::size_t numDisks() const; // 众多成员函数之一
...
};
extern FileSystem tfs; // 需要给其他编译单元使用的对象
class Directory {
public:
Directory(params);
...
};
Directory::Directory(params)
{
...
//使用其他编译单元的tfs对象,可能会出现tfs未被初始化的情况,导致出问题
std::size_t disks = tfs.numDisks();
...
}
Directory tempDir(params); // 创建对象
该写法,要求 tfs 在 tempDir 之前被初始化,否则会出错!
解决方式: 采用单例模式
class FileSystem { ... }; // 同前面
//用该函数来替换tfs对象;它在FileSystem class中可能是个static
FileSystem& tfs()
{
static FileSystem fs; // 定义并初始化一个local static对象
return fs; // 返回一个reference指向上述对象
}
class Directory { ... }; // 同前面
Directory::Directory(params)
{
...
std::size_t disks = tfs().numDisks();
...
}
//用该函数来替换tempDir对象;它在Directory class中可能是个static
Directory& tempDir()
{
static Directory td;
return td;
}
这么修改之后,这个程序可以像以前一样地使用,唯一不同的是他们现在使用 tfs() 和 tempDir() 而不再是 tfs 和 tempDir。也就是说他们使用函数返回的“指向 static 对象”的 references,而不再使用 static 对象自身。
注意: 从另一个角度看,这些函数“内含 static 对象”的事实使它们在多线程系统中带有不确定性。再说一次,任何一种 non-const static 对象,不论它是 local 或 non-local,在多线程环境下“等待某事发生”都会有麻烦。处理这个麻烦的一种做法是:在程序的单线程启动阶段(single-threaded startup portion)手工调用所有 reference-returning 函数,这可消除与初始化有关的“竞速形势(race conditions)”。
构造/析构/赋值运算
几乎你写的每一个 class 都会有一或多个构造函数、一个析构函数、一个拷贝赋值操作符。
评论区