条款04:确定对象被使用前已先被初始化

最佳的处理方法:永远在使用对象的之前先将他初始化,对于无任何成员的内置类型,你必须手工完成此时;对于内置类型以外的任何其他东西,初始化责任落在构造函数身上:确保每一个构造函数都将对对象的每一个成员初始化。

但是别混淆赋值(assignment)和初始化(initialization)。

//内置类型如下
int x = 0;								//对int进行手工初始化
const char* text = "A C-style string"; 	//对指针进行手工初始化

double d;
std::cin >> d;							//以读取input stream的方式完成初始化
	
	
//非内置类型如下
//考虑如下一个表现通讯薄的class:
class PhoneNumber{...};
class ABEntry   //ABEntry = "Address Book Entry"
{
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> thePhone;
	int numTimesConsulted;
};

ABEntry::ABEntry(const std::string& name,const std::string address,
			const std::list<PhoneNumber>& phones)
{
		theName = name;			//这些都是赋值,而非初始化
		theAddress = address;
		thePhone = phones;
		numTimesConsulted = 0;
}

ABEntry的构造函数的一个较佳的写法是使用所以的member initialization list(成员初始化)替代赋值操作。这样做的和上述最终结果一样,但是通常效率更高:

ABEntry::ABEntry(const std::string& name,const std::string address,
			const std::list<PhoneNumber>& phones):
			theName(name),				//这些是初始化
			theAddress(address),
			thePhone(phones),
			numTimesConsulted(0)
{
	
}

对大多数类型而言,先调用default构造函数然后再调用copy assignment操作符,单只调用一次copy构造是比较高效的,有时候高效很多。

规定:总是在成员初始化列表中列出所有成员,以免还的记住哪些成员变量可以无需初始化。例如,由于numTimesConsulted属于内置类型,如果成员初始化列表(member initialization list)遗漏了它,他就没初值,因而可能开启不明确行为。

有时候即使面对的成员变量属于内置类型(那么其初始化和赋值得到成本相同),也一定的使用成员初始化列表:const或references。所以总是使用成员初始化列表,这样做有时候绝对必要,而且往往比赋值搞笑。

C++有着十分固定的“成员初始化次序”。次序总是相同的:base classes更早于其derived classes被初始化,而class的成员变量总是以其声明次序被初始化。为了不必要的错误,当你的成员初始化列表中列出各个成员时候,最好总是以其声明次序为次序。

对于static对象而言,其寿命从被构造出来知道程序结束为止。这种对象包括global对象,定义于namespace作用域内的对象,在classes内、在函数内、以及file作用域内被声明为static的对象。函数内的static对象称为local static对象,其他的static对象称为non-local static对象。程序结束时static对象被自动销毁,也就是它们的析构会在main结束时自动调用。

现在关心的问题:设计至少两个源码文件,每一个内含至少一个non-local static对象,如果某个编译单元内的某个non-local static对象的初始化动作使用了另一个编译单元内的某个non-local static对象,它所用到的这个对象可能尚未被初始化。因为C++对“定义于不同编译单元内的non-local static对象”的初始化次序无明确定义。

如下假如你有一个FileSystem class,它让互联网上的文件看起来像位于本机(local)。由于这个class,可能会产生一个特殊对象,位于global或namespace作用域内,象征单一文件系统:

//程序库
class FileSystem //来自你的程序库
{
public:
	...
	std::size_t numDisks() const; //众多成员函数之一
	...
};

extern FileSystem tfs; //预备给客户使用的对象,tfs代表“the file system”

//客户代码
class Directory //由程序库客户建立
{
public:
	Directory(params);
	...
};

Directory::Directory(params)
{
	...
	std::size_t disks = tfs.numDisks(); //使用tfs对象
}

Directory tempDir(params);	//为临时文件而做出的目录

现在,初始化顺序的重要性出来了:除非tfs在tempDir之前先被初始化,某则tempDir的构造函数会用到尚未初始化的tfs。但tfs和tempDir是不同的人在不同的时间于不同的源码建立起来的,它们定义于不同的编译单元内的non-local static对象,你无法确定tfs会在tempDir之前被初始化。

对于这种问题,唯一要做的是:将每个non-local static对象搬到自己的专属对象内(该函数被声明为static)。这些函数返回一个reference指向它所含的对象,然后用户直接调用这些函数。这就是Singleton设计模式。

这个手法的基础在于:C++保证,函数内的local static对象会在“该函数被调用期间”“首次遇上该对象定义式”时被初始化。用此技术施行于tfs和tempDir身上,结果如下:

class FileSystem{...};
FileSystem& tfs()   		//这个函数用来替代tfs对象
{							//它在FileSystem中可能是static
	static FileSystem fs;
	return fs;
}

class Directory{...};
Directory::Directory(params)
{
	...
	std::size_t = tfs().numDisks();
	...
}

Directory& tempDir()		//用这个函数替代tempDir对象
{							//它在Directory中可能是static
	static Directory td;
	return td;
}

这种结构下的reference-returning函数往往十分单纯:第一行定义并初始化一个local static对象,第二行返回它。这种可以成为绝佳的inline候选人,尤其被频繁调用。但从另外一给角度看,这些函数内含static对象,它们在多线程中带有不确定性。处理这种麻烦的方法一种做法:在程序的单线程启动阶段手工调用所谓reference-returning函数,这可消除与初始化有关的条件竞争。

总结

  • 为内置类型对象进行手工初始化,因为C++不保证初始化它们。
  • 构造函数最好使用成员列表初始化(member initialization list),而不要在构造函数本体内使用赋值操作。成员初始化列表列出的成员变量,其排列次序应该和它们在class中声明次序相同。
  • 为免除“夸编译单元之初始化次序”问题,请以local static对象替换non-local static对象。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值