文章目录
term4:Make sure that objects are initialized before they’re used
为什么对象使用之前要进行初始化,举个例子
int x;
在某些语境之下,x会被初始化为0。但在其他语境中x的值是不能被保证的。这就会导致不明确的行为,进行给你的程序带来未知的风险,这是需要避免的。未知的情况千变万化,但解决的办法显得“大道至简”——在你使用对象之前,将其初始化即可。
(1)内置数据类型
int x = 0;
const char* text = "A C-style string"; //手工初始化
(2)自定义数据类型
使用成员初始值列表来替换赋值操作:
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 num;
}
//赋值操作
ABEntry(const std::string &name,const std::string& address,const std::list<PhoneNumber>& phones)
{
theName = name;
theAddress = address;
thePhones = phones;
num = 0; //赋值操作
}
/*赋值操作其实实施了2个步骤:
(1)调用default构造函数设立初值(default constructor)
(2)再对变量予以赋值(copy assignment)*/
//成员初始化值列表
ABEntry(const std::string &name,const std::string& address,const std::list<PhoneNumber>& phones)
:theName(name);
theAddress(address);
thePhones(phones);
num(0); //初始化操作
{ } //构造函数不必有任何的动作
2个操作的结果是相同的,但是成员初值列的操作效率更高。赋值操作首先调用了default构造函数为其中的变量设立了初始值,然后立刻再对他们赋予新的数值。而成员初始化列表操作,只调用了一次copy构造函数是比较高效的。
需要注意的是,成员初始化列表的顺序是相对固定的,为了避免不必要的麻烦,初始化顺序可以遵循声明的次序。
(3)使用local_static对象替换none_local_static对象
书中说的比较晦涩,不同编译单元内定义之non-local static 对象的初始化次序,光看着就很头疼。首先要知道什么是local_static对象,什么是none_local_static对象。
在classes内部,函数内,file作用域内被声明为staic的对象被称为local_static对象;其他static对象被称为none_local_static对象。
而编译单元,指的是单一源码文件加上其所包含的头文件。而不同的编译单元,说明至少涉及2个编译单元;C++对于不同编译单元内的none_local_static对象的初始化次序并没有明确的定义。所以在程序运行过程中,某个编译单元内的none_local_static对象在初始化动作中使用了另一个编译单元内的某个none_local_static对象,他所用到的这个对象还没有初始化,这就带来了问题。
举个栗子:
编译单元A内:
class FileSystem{
public:
...
std:size_t numdisks() const; //成员函数
...
};
extern FileSystem tfs;
//预留给用户使用对象
编译单元B内:
class Directory
public:
{
Directory(params);
...
};
Directory::Directory(params){
...
std::size_t disks = tfs.numDisks(); //使用tfs对象
}
之前讲过C++对于不同编译单元内的none_local_static对象的初始化次序并没有明确的定义,所以上述程序运行时会出现不可预料的错误。既然无法保证初始化次序,能否明确初始化顺序来达到这一目的呢,C++目前没有机制可以保证这一点,无法做到。
解决方法: 将none_local_static对象搬到一个专属函数内(该对象在函数内被声明为static),这些函数返回一个reference指向他所含的对象。类似于单例模式的手法。
在使用过程中,用户直接调用这些函数,而不是直接调用对象。
编译单元A内:
class FileSystem{...};
FileSystem& tfs(){
static FileSystem fs;
return fs;
}
编译单元B内:
class Directory{...};
Directory::Directory(params){
...
std::size_t disks = tfs().numDisks();
...
}
Directory& tempDir(){
static Directory td;
return td;
}
这在单线程中是ok的,如果是在多线程系统中仍然有一定的不确定性,因为他在调用对象的过程中,在等待他进行初始化。这种情况是比较危险的,会有意想不到的错误发生。
2、总结
书山有路勤为径,学海无涯苦作舟。
3、参考
3.1 《Effective C++》