最佳处理方法:永远在使用对象之前先将它初始化.
int x = 0;
const char* text = "A C-style string";
double d;
std::cin >> d;
对于构造函数:确保每一个构造函数都将对象的每一个成员初始化。
//区分赋值和初始化
例如以下构造函数:
AB::AB(const std::string& name, const std::string& address, const std::list<PhoneNumber>& phones)
{
//以下几个左值都是类的成员
theName = name;
theAddress = address;
thePhones = phones;
numTimesConsulted = 0;
//敲黑板!!以上都是赋值,不是初始化!!
}
这会导致AB对象带有你期望的值,不是最佳做法!
构造函数的最佳写法:使用所谓的member initialization list(成员初值列) 替换赋值动作
AB::AB(const std::string& name, const std::string& address; const std::list<PhoneNumber>& phones)
:theName(name), theAddress(address), thePhones(phones), numTimesConsulted(0) {}
//通过copy构造初始化
现在,这些都是初始。这个构造函数和上一个的最终结果相同,但通常效率较高。
默认构造函数也可以使用成员初值列。
AB::AB() : theName(), theAddress(), thePhones(), numTimesConsulted(0) {} //没有指定初值则自动调用default构造函数
重要规则:规定总是在初值列中列出所有成员变量。以免还得记住哪些成员变量可以无需初值。
如果成员初值列遗漏某个成员,它就没有初值,因此可能开启”不明确行为“的潘多拉盒子~
若成员变量是const或reference,就一定需要初值,不能被赋值!
所以最简单的做法就是:总是使用成员初值列。这样做有时候绝对必要,且又往往比赋值更高效。
C++有着固定的”成员初始化次序“:
base classes基类总是比derived classes继承类更早被初始化。
class的成员变量总是以其声明次序被初始化。
例如:
class AB {
public:
AB::AB(const std::string& name, const std::string& address, const std::list<PhoneNumber>& phones)
:theName(name), theAddress(address), thePhones(phones), numTimesConsulted(0) {}
private:
std::list<PhoneNumber> thePhones;
std::string theAddress;
int numTimesConsulted;
std::string theName;
}
即使在构造函数成员初值列中出现的次序和private的声明次序不同,初始化次序依旧和声明次序相同(即thePhones最先初始化)
为避免你或读者被迷惑,并避免某些可能存在的晦涩错误,当在成员初值列中条列各个成员时,最好总是以其声明次序为次序!
”不同编译单元内定义之non-local static对象”的初始化次序:
static对象,其寿命从被构造出来直到程序结束为止。
这种对象包括:global对象,定义于namespace作用域内的对象,在class内,在函数内,以及在file作用域内被声明为static的对象。
函数内的static对象称为local static对象(对函数而言是local),其它对象称为non-local static对象。
static对象在程序结束时自动销毁,它们的析构函数会在main()结束时被自动调用。
//补充:所谓编译单元是指产出单一目标文件的那些源码。基本上它是单一源码文件加上其所含入的头文件。
真正的问题是:如果某编译单元内的某个non-local static对象的初始化动作使用了另一个编译单元内的某个non-local static对象,
它所用到的这个对象可能尚未被初始化,因为C++对定义在不同编译单元内的non-local static对象的初始化次序并无明确定义。
实例:
class FileSystem { //来自你的程序库
public:
...
std::size_t numDisks() const; //众多成员函数之一
...
};
extern FileSystem tfs; //预备给客服使用的对象
//tfs代表"the file system"
class Directory {
public:
Directory( params );
...
};
Directory::Directory( params )
{
...
std::size_t disks = tfs.numDisks(); //使用tfs对象
}
然后执行:
Directory tempDir( params ); //为临时文件而做出的目录
现在,初始化次序的重要性显示出来了:除非tfs在tempDir之前先被初始化,否则tempDir的构造函数会用到尚未初始化的tfs。
如何确定tfs在tempDir之前先被初始化?
唯一需要做的是:
将每个non-local static对象搬到自己的专属函数内(该对象在此函数内被声明为static)。
这些函数返回一个reference指向它所含的对象。然后用户调用这些函数,而不直接指涉这些对象。
即:non-local static 被 local static对象替换了。
这种方法基于:C++保证,函数内的local static对象会在 “函数被调用期间" 或 "首次遇上该对象之定义式” 时被初始化!
所以以"函数调用“(返回一个reference指向local static对象) 替换”直接访问non-local static对象".
这样获得的reference将指向一个历经初始化的对象。
这个手法的基础在于:C++保证,函数内的local static 对象会在"该函数被调用期间""首次遇上该对象的定义式"时被初始化.所以如果以"函数调用"(返回一个reference指向local static 对象)替换"直接访问non-local static 对象",就可保证所获得的那个reference将指向一个已经初始化的对象.更棒的是,如果从未调用non-local static 对象的"仿真函数",就绝不会引发构造和析构成本:真正的non-local static 对象没有这个优点.
所以,经过此技术施行:
class FileSystem { //来自你的程序库
public:
...
std::size_t numDisks() const; //众多成员函数之一
...
};
FileSystem& tfs() //这个函数用来替换tfs对象:它在FileSystem class中可能是个static.
{
static FileSystem fs; //定义并初始化一个local static对象
return fs; //返回一个reference指向上诉对象。
}
class Directory {
public:
Directory( params );
...
};
Directory::Directory( params )
{
...
std::size_t disks = tfs().numDisks(); //使用tfs()对象
}
Directory& tempDir() //这个函数用来替换tempDir对象,在Directory class中可能是个static
{
static Directory td; //定义并初始化local static对象
return td; //返回一个reference指向上述对象
}
现在这个程序调用就没有问题了,唯一不同的是现在使用tfs()和tempDir()而不再是tfs和tempDir.
也就是说使用函数返回的"指向static对象"的reference,而不再使用static对象本身。
注意:任何一种non-const static对象,不论它是local或non-local,在多线程环境下“等待某事发生”都会有麻烦!
处理麻烦的一种做法:在程序的单线程启动阶段手工调用所有reference-returning函数。
/* 补充,内置类型:包括算术类型和空类型在内的基本数据类型。
算术类型包括:字符型,整型,bool型,浮点型 */
为避免在对象初始化之前过早地使用它们,需要做三件事:
第一,手工初始化内置型non-member对象。
第二,使用成员初值列对付对象的所有成分。
第三,在“初始化次序不确定性”氛围下加强设计!
总结:
-对内置型对象进行手工初始化,因为C++不保证初始化它们。(避免出现不确定情况)
-构造函数最好使用成员初值列,而不要在构造函数本体内使用赋值操作。初值列列出的成员变量,其排列次序应该和它们在class中的 声明次序相同。
-为免除“跨编译单元之初始化次序"问题,请以local static对象替换non-local static对象。