Item 04:Make sure that opjects are initialized before they‘re used
关于“将对象初始化”这事,C++似乎反复无常。如果你这么写:
int x;
在某些语境下x保证被初始化(为0),但在其他语境中却不保证。如果你这么写:
class Point{
int x,y;
};
...
Point p;
p的成员变量有时被初始化,有时候不会。
通常如果你使用C part of C++而且初始化可能招致运行期成本,那么就不保证发生初始化。一旦进入non-C part of C++,规则有些变化。这就很好地解释了为什么array(来自C part of C++)不保证其内容被初始化,而vector(来自STL part of C++)却有此保证。
表面上这似乎是个无法决定的状态,而最佳的处理方法就是:永远在使用对象之前先将它初始化。例如:
int x = 0;
const char* text = “A C-style string”;
double d;
std::cin>>d; //以读取的方式完成初始化
至于内置类型以外的任何其他东西,初始化责任则落在构造函数身上。规则很简单:确保每一个构造函数都将每一个成员初始化。规则很容易奉行,但是重要的是别混淆了赋值和初始化。如:
clall 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;//这些都是赋值,不是初始化
}
C++规定,对象的成员变量的初始化动作发生在进入构造函数本体之前。ABEntry构造函数的一个较佳写法是,使用成员初值列来替换赋值动作
ABEntry::ABEntry(const std::string& name,const std::string& address,const std::list<PhoneNumber>& phones)
:theName(name);
theAddress(address);//现在,这些都是初始化
thePhones(phones);
numTimesConsulted(0)
{}//现在,构造函数本体不必有任何动作
对大多数类型而言,比起先调用defult构造函数然后再调用copy assignment操作符,单只调用一次copy构造函数式比较高效的。
许多classes拥有多个构造函数,每个构造函数有自己的成员初值列。如果这种classes存在许多成员变量/或base classes,多份成员初值列的存在就会导致不受欢迎的重复和无聊的工作。这种情况下可以合理地在初值列中遗漏那些“赋值表现像初始化一样好”的成员变量,改用它们的赋值操作,并将那些赋值操作移往某个函数,供所有构造函数调用。
函数内的static对象称为local static对象(因为它们对函数而言是local),其他static对象称为non-local static对象。
由于C++对“定义于不同的编译单元的non-local static”对象的初始化相对次序并无明确定义,因此可能会产生这样一个问题:如果某编译单元内的某个non-local static对象的初始化动作使用了另一编译单元的某个non-local static对象,它所用到的这个对象可能尚未初始化。请看如下代码:
class FileSystem
{
public:
...
std::size_t numDisks() const;
...
} ;
extern FileSystem tfs;//预备给客户使用的对象
class Directory
{
public:
Directory(params);
...
};
Directory::Directory(params)
{
...
std::size_t disks=tfs.numDisks();//使用tfs对象
...
};
Directory tempDir(params);
现在,问题出来了,除非tfs在tempDir之前先被初始化,否则tempDir的构造函数会用到未被初始化的tfs。
使用Singleton模式可以解决这个问题。
class FileSystem{...};//同前
FileSystem& tfs() //这个函数用来替换tfs对象
{
static FileSystem fs;
return fs;
}
class Directory{...};//同前
Directory::Directory(params)
{
...
std::size_t disks = tfs().numDisks();
...
}
Directory& tempDir()//这个函数用来替换temoDir对象
{
static Directory td;
return td;
}
请记住:
1.为内置型对象进行手工初始化,因为C++不保证初始化它们。
2.构造函数最好使用成员初值列,而不要在构造函数本体使用赋值操作。初值列列出的成员变量,其排列次序应该和它们在class中的声明次序相同。
3.为免除“跨编译单元之初始次序:问题,请以local static对象替换non-local static 对象