条款04:确定对象被使用前已先被初始化(Make sure that objects are initialized before they're used)
内容:
在C++语言中,读取未初始化的值会导致不明确的行为,在某些平台上,仅仅只是读取未初始化的
值,就可能让你的程序终止运行。更可能的情况是读入一些“半随机”bits,污染了正在进行读取动作的
那个对象,最终导致不可预知的程序行为,以及许多令人不愉快的调试过程。
其实我们处理这种情况的办法很简单就是:永远在使用对象之前先将它初始化。而对于一些无任
何成员的内置类型(基本数据类型),你必须手工完成此事。而对于内置型的任何其他东西,初始化责任
就会落在构造函数(constructors)身上。规则当然也很简单:确保每一个构造函数都将对象的每一个成
员初始化。
这里我们需要对初始化和赋值作一个区别,很多人在这里会发生混淆,我们先来看下面这个例子:
class Person{
public:
Person(string& name,bool isMale,string& contactPhoneNum,
string& homeAddress,int ages){
//注意哦,这里是赋值而不是初始化
name_ = name;
isMale_ = isMale;
contactPhoneNum_ = contactPhoneNum;
homeAddress_ = homeAddress;
ages_ = ages;
}
...
private:
string name_;
bool isMale_;
string contactPhoneNum_;
string homeAddress_;
int ages_;
};
为什么会说这里不是初始化而是简单的赋值呢?因为对象的成员变量初始化动作发生在进入构造函
数之前,如果要实现变量的初始化应该这样改:
Person::Person(string& name,bool isMale,string& contactPhoneNum,
string& homeAddress,int ages)
:name_(name),isMale_(isMale),contactPhoneNum_(contactPhoneNum),
homeAddress_(homeAddress),ages_(ages){
}
上面这两种形式虽然得到的效果是一样的,不过初始化的效率更高.因为基于赋值的构造函数是先调
用默认构造函数然后再进行变量的赋值,而直接初始化变量的构造函数只用一步就完成了对象的构造过程
.这里我们需要注意的地方是各个成员变量的初始化顺序只与它们在类中声明的顺序保持一致,与它们在
构造函数中出现的次序没有关系.
讲到这里,有个问题出来:当我使用一个对象时,我无法确定该对象是否已经被初始化构造出来了.对
于这种情形的很典型的一个例子就是针对于"不同编译单元内的non-local static 对象的初始化顺序",该类型对象从程
序启动时被构造出来,在程序结束的时候(退出main函数)时自动调用析构函数释放memory,这样我们怎么
用它呢?我来举个例子:
一般操作系统中都存在唯一的一个内存管理器MemoryManager,它管理着各个进程所占用的memory的数
目,而我们现在有个随操作系统启动服务,它负责记录系统中各个应用程序的产生各种SystemLogger,而当
系统内存低于30M的时候,这个服务不能够被创建.
class MemoryManager{
public:
std::size_t getAvailablePhysicalMemory()const{
return leftPhysicalMemory_;
}
...
private:
std::size_t leftPhysicalMemory_;
...
};
MemoryManager g_memoryManager;//only a instance exists in local system
class LoggerGenerator{
public:
LoggerGenerator(){
...
std::size_t memoryNum=g_memoryManager.getAvailablePhysicalMemory();
...
}
};
LoggerGenerator g_loggerGenerator;//only a instance exists in local system too
到这里我们遇到了一个问题,g_memoryManager与g_loggerGenerator都是global对象,随系统启动,而
g_loggerGenerator又依赖与g_memoryManager的对象,所以说g_loggerGenerator对象应该在g_memoryMa-
nager对象之后产生,但是它们两个又是non-local static对象,它们的构造顺序我们无法预知,所以如果
要是g_loggerGenerator那么系统必然出现意想不倒的错误,随后带来的就是调试时间的花费,怎么办呢?真
是个头疼的问题?嗯哼,有了!只要我们把non-local static对象的产生放到一个函数当中,我们就可以解决
这个问题,于是我们添加两个创建对象函数:
MemoryManager& getMemoryManager(){
static MemoryManager memoryManager;
return memoryManager;
}
LoggerGenerator& getLoggerGenerator(){
static LoggerGenerator loggerGenerator;
return loggerGenerator;
}
在loggerGenerator构造函数中我们就得这样去调用它:
class LoggerGenerator{
public:
LoggerGenerator(){
...
std::size_t memoryNum=getMemoryManager.getAvailablePhysicalMemory();
...
}
};
仔细想想为什么这样就不会出现对象初始化先后的问题了,这样就能保证产生LoggerGenerator对象之前,
MemoryManager对象一定产生了,因为local static对象来说当你第一次调用它是它才会产生对象,如果该对象
已经存在(已经被初始化构造出来)那么它就用已经存在的这个,这样的好处就保证了不管我们什么时候调用这
个函数它总是会返回一个存在对象的引用的,呵呵!
今天我们就聊到这里!
请记住:
★ 为内置型对象进行手工初始化,因为C++不保证初始化它们.
★ 构造函数最好使用成员初值表(member initialization list),而不要在构造函数本体内使用赋值操作
(assignment).初值列列出的成员变量,其排列次序应该和它们在class中声明次序相同.
★ 为免除"跨编译单元之初始化次序"问题,请以local static对象替换non-local static对象.