实用经验 6 确保每个对象在使用前已被初始化

本文探讨了C++中对象初始化的不确定性,强调了内置类型手动初始化、构造函数使用成员初始化列表以及避免non-local static对象初始化问题的重要性。通过示例展示了如何将non-local static对象转换为local static以解决初始化顺序问题,提醒程序员注意这些最佳实践以确保代码的正确性和效率。
摘要由CSDN通过智能技术生成

关于“将对象初始化”这事情,C++似乎反复无常。如果你定义一个int变量,编写这样的代码:

int x;

在某些语境下x保证会初始化为0,但在其他语境下却不保证。如果你编写了这样的代码:

Class CPoint    // 二维点数据类
{
	int m_iX;   // 二维点的x坐标。
	int m_iY;   // 二维点的y坐标
}

CPoint pt;  // 声明一个点pt

pt的成员变量有时会被初始化(为0),有时候不会。如果你是来自其他语言阵营的编程人员,那么请注意这点。因为这点颇为重要。

读取未初始化的对象会导致不确定的行为,在某些情况下,读取为初始化的对象会让你的程序终止运行,但在另外一些情况下会读入一些随机的bits,最终导致不可预知的程序行为。这种现象一般表现为程序可正常执行,但是执行结果有时正确有时错误,无任何规律可言。

对于“对象的初始化何时一定发生,何时不一定发生”已经有一些规则了。但是不幸的是这些规则都过于复杂,不利于记忆。通常如果你使用的是C++中的C部分,由于对象的初始化可能会招致运行期的成本,那么对象就不保证会初始化。但C++中的非C部分,这个规则就发生变化了。对象一般会发生初始化。这就可以很好的解释了为什么数组不会发生初始化,而来自stl的vector却会发生初始化过程。

从表面上看,对象在使用前是否会被初始化,这是无法确定的。而最佳的处理方法是:永远在对象被使用之前将它初始化,对于无任何成员的内置数据类型,你必须手动完成初始化。例如:

int i = 5;                //  对int进行手动初始化
char *pszStr = “a c strig”; // 对指针进行手动初始化。

对于内置类型以外的任何其他东西,初始化的责任落到了对象的构造函数身上。其实规则很简单:保证每个对象在构造时初始化该对象的每个成员。

这个规则很简单,也很容易执行。但这儿容易混淆的是赋值和初始化。考虑CPerson类的构造函数实现。

typedef enum tagSex      		// 性别枚举类型 
{
    MALE_SEX = 0//   MALE_SEX表示男性
    FEMALE_SEX,  		//   MALE_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_stName(strName)
, m_sex(sex)
{
}

这种构造函数和上一个构造函数最终的结果一样,但是效率更高。

赋值和列表初始化区别

  • 使用赋值初始化对象变量时,在构造函数执行前会调用默认构造函数初始化m_strName和m_sex,然后再立刻执行赋值操作。这默认构造函数的所做的一切都因此浪费了。通过初始化列表做法,避免了重复操作。所以第二种实现效率更高。
  • 有些情况下,即是赋值和初始化列表两者效率一样,也得使用初始化列表。如果成员变量是const或reference,他们就一定要初始化,而不能被赋值。
  • 由于C++有着固定的初始化顺序:基类先于子类初始化,class中的变量总是以变量声明的顺序初始化,和成员初始化列表顺序无关。因此在成员初始列中条列各变量时,最好以声明次序为顺序。

最后介绍non-local static对象初始化问题。为了说明non-local static对象初始化问题,考虑下述示例代码:

// CFileSystem文件系统类声明文件FileSystem.h
class CFileSystem 
{
public:
    const int GetDiscCount(); 
    
private:
	int  m_nDiscCount;
};

// CFileSystem文件系统类实现文件FileSystem.cpp
const int CFileSystem::GetDiscCount()
{
	// 获得磁盘数量
	m_nDiscCount = ...
	
	return m_nDiscCount;
}
CFileSystem fileSystem;


// CDirectory 文件系统类声明文件Directory.h
class CDirectory
{
public:
	CDirectory();
	
public:
    const int GetDiscCount(); 
	
private:
	int  m_nDiscCount;
};

// CDirectory文件系统类实现文件Directory.cpp
extern CFileSystem fileSystem;
CDirectory::CDirectory()
{
	m_nDiscCount = fileSystem.GetDiscCount();
}

const int CDirectory::GetDiscCount()
{
	return m_nDiscCount;
}

CDirectory directory;

// main.cpp 
extern CDirectory directory;
int main()
{
	cout << directory.GetDiscCount()<<endl;
	system("pause");
}

上述代码中CDirectory directory依赖全局对象CFileSystem fileSystem,由于C++标准“对定义于不同的编译单元内的non-local static对象”的初始化相对次序并没有明确定义,所以很有可能在Directory.cpp里的变量directory初始化早于定义在FileSystem.cpp中的fileSystem,这时directory就得不到预期想要的值。这就是“non-local static”问题。

既然出现了“non-local static”问题,那如何解决这个问题呢?这里笔者的建议是把所有“non-local static”变量尽量转换为“local static”变量,通过函数接口为外界提供服务。

// CFileSystem文件系统类声明文件FileSystem.h
class CFileSystem 
{
public:
    const int GetDiscCount(); 
    
private:
	int  m_nDiscCount;
};

// CFileSystem文件系统类实现文件FileSystem.cpp
const int CFileSystem::GetDiscCount()
{
	// 获得磁盘数量
	m_nDiscCount = ...
	
	return m_nDiscCount;
}

CFileSystem& fileSystem()
{
	static CFileSystem fileSystem;
	return fileSystem;
}

// CDirectory 文件系统类声明文件Directory.h
class CDirectory
{
public:
	CDirectory();
	
public:
    const int GetDiscCount(); 
	
private:
	int  m_nDiscCount;
};

// CDirectory文件系统类实现文件Directory.cpp
extern CFileSystem& fileSystem();
CDirectory::CDirectory()
{
	m_nDiscCount = fileSystem().GetDiscCount();
}

const int CDirectory::GetDiscCount()
{
	return m_nDiscCount;
}

CDirectory& directory()
{
	static CDirectory directory;
	return directory;
}

// main.cpp 
extern CDirectory& directory();
int main()
{
	
	cout << directory().GetDiscCount()<<endl;
	system("pause");
}

既然这样,为避免在对象初始化之前过早的使用它们,你需要做三件事情:第一,手动初始化内置类型对象,第二,使用成员初始化列表初始化对象的所有成分,最后在初始化次序不确定情况下,加强你的设计,避免类似non-local static对象初始化这样的问题。

请谨记

  • 为内置类型对象进行手动初始化,因为C++不保证初始化他们。
  • 构造函数最好使用成员初始化列,而不是在构造函数本体内使用赋值操作。初值列表列出的成员变量,其排列顺序应和他们在class中的声明次序相同。
  • 警惕类似non-local static对象初始化问题,把所有“non-local static”变量尽量转换为“local static”变量,通过函数接口为外界提供服务。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值