关于“将对象初始化”这个问题,C++似乎反复无常。如果你定义一个int变量,编写这样的代码:
int x;
在某些语境下x保证会初始化为 0,但在其他语境下却无法保证。如果你编写了这样的代码:
Class CPoint //二维点数据类
{
intmiX; //二维点的x坐标
int m_iY; //二维点的y 坐标
}
...
CPointpt; //声明一个点pt
pt的成员变量有时会被初始化(为0),有时候不会。如果你是来自其他语言阵营的编程人员,那么请注意这点,因为这点颇为重要。
读取未初始化的对象会导致不确定的行为。在某些情况下,读取未初始化的对象会让你的程序终止运行,但在另外一些情况下会读入一些随机的bits,最终导致不可预知的程序行为。这种现象一般表现为程序可正常执行,但是执行结果有时正确有时错误,无任何规律可言。
对于“对象的初始化何时一定发生,何时不一定发生”已经有一些规则了,但是不幸的是这些规则都过于复杂,不利于记忆。通常如果你使用的是C++中的 C部分,由于对象的初始化可能会招致运行期的成本,那么对象就不保证会初始化。但对于C++中的非C部分,这个规则就发生变化了,对象一般会发生初始化。这就可以很好地解释为什么数组不会发生初始化,而来自stl的vector 却会发生初始化过程。
从表面上看,对象在使用前是否会被初始化是无法确定的,而最佳的处理方法是:永远在对象被使用之前将它初始化;对于无任何成员的内置数据类型,必须手动完成初始化。例如:
int1=5; //对int进行手动初始化
char*pszStr="a c strig”; // 对指针进行手动初始化
对于内置类型以外的任何其他成员,初始化的责任落到了对象的构造函数上。其实规则很简单:保证每个对象在构造时初始化该对象的每个成员。
这个规则很简单,也很容易执行。但这里容易混淆的是赋值和初始化。考虑CPerson 类的构造函数实现如下:
typedef enum tagSex //性别枚举类型
{
MALE SEX=0, //MALE SEX表示男性
FEMALE SEX, //FEMALE SEX表示女性
}Sex;
Class CPerson //CPerson类型实现人的描述:名字和性别
{
std::string m_strName; //名字
Sex m_sex; //性别
CPerson::CPerson(std::string &strName,Sex &sex) // CPerson类赋值型构造函数
{
m strName - strName;
m sex=sex:
}
这种实现会导致CPerson 对象带有你期望的值,但这种实现方法并不是一种最佳的实现方法。因为 C++规定对象中成员变量的初始化发生在对象的构造函数之前,所以 CPerson 构造函数中 m strName 和 m sex都不是初始化,而是赋值。
C++对象的构造函数一个较佳的写法是使用成员初始化列表替换赋值动作:
CPerson::CPerson(std::string &strName, Sex &sex)
:m _strName(strName)
,m_sex(sex)
{
}
这种构造函数和上一个构造函数最终的结果一样,但是效率更高。
赋值和列表初始化的区别:
●使用赋值初始化对象变量时,在构造函数执行前会调用默认构造函数初始化m_strName 和 m_sex,然后再立刻执行赋值操作。这样默认构造函数所做的一切都因此浪费了。通过初始化列表做法,避免了重复操作。所以第二种实现效率更高。
●有些情况下,即使赋值和初始化列表两者效率一样,也得使用初始化列表。如果成员变量是const 或 reference,它们就一定要初始化,而不能被赋值。
●由于 C++有着固定的初始化顺序:基类先于子类初始化,class 中的变量总是以变量声明的顺序初始化,和成员初始化列表顺序无关,因此在成员初始化列表中初始化各变量时,最好以声明次序为顺序。
最后介绍non-local static对象初始化问题。为了说明该问题,考虑设计模式中Singleton 模式的实现方法。
//CFileSystem 文件系统类声明文件 FileSystem.h
//文件系统类声明
Class CFileSystem
{
public
//单例模式示例获得接口
static CFileSystem *Instance():
char*GetRootName();//获得文件系统root名称
};
// CFileSystem 文件系统类实现文件 FileSystem.cpp
//单例模式示例获得接口实现
static CFileSystem*Instance()
{
static CFileSystem tfs;
return &tfs;
}
// CFileSystem 单例模式使用文件main.cpp
CFileSystem *pFileSystem =CFileSystem:Instance):
char*pszRootName =pFileSystem->GetRootName):
如果你熟悉多线程编程,你也许已经看出问题了。Instance 静态函数不具有线程安全,在多线程情况下带有不确定性,这就是 non-local static 对象初始化问题。其实在多线程下“等待某事的发生”是一件非常麻烦、代价很高的事情。处理这个麻烦事情的一种做法是:在程序单线程启动阶段手动调用实现 non-local static 对象初始化。
既然这样,为避免在对象初始化之前过早地使用它们,你需要做3件事情:第一,手动初始化内置类型对象;第二,使用成员初始化列表初始化对象的所有成分;第三,在初始化次序不确定的情况下,加强你的设计,避免类似non-localstatic 对象初始化问题的发生。
请谨记
●为内置类型对象进行手动初始化,因为 C++不保证初始化它们。
●构造函数最好使用成员初始化列,而不是在构造函数本体内使用赋值操作。初值列表列出的成员变量,其排列顺序应和它们在class中的声明次序相同。