侧边栏壁纸
博主头像
如此肤浅

但行好事,莫问前程!

  • 累计撰写 52 篇文章
  • 累计创建 6 个标签
  • 累计收到 6 条评论

目 录CONTENT

文章目录

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

如此肤浅
2022-05-14 / 0 评论 / 0 点赞 / 35 阅读 / 2,586 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2022-05-15,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

1. 构造函数最好使用成员初值列,而不要在构造函数本体内使用赋值操作。
2. 初值列列出的成员变量,其排列次序应该和它们在 class 中的声明次序相同。
3. 为免除“跨编译单元之初始化次序”问题,请以 local static 对象替换 non-local static 对象。

对象内的成员通常采用构造函数进行初始化,但要注意赋值(assignment)和初始化(initialization)的区别!!!

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 numTimesConsulted;
};
ABEntry::ABEntry(const std::string& name, const std::string& address, 
			const std::list<PhoneNumber>& phones)
{
	// 以下都是赋值,而不是初始化
	theName = name;
    theAddress = address;
    thePhones = phones;
    numTimesConsulted = 0;
}

上面虽然会让 ABEntry 对象带有你期望的值,但不是最佳做法。C++规定,对象的成员变量的初始化动作发生在进入构造函数本体之前。在 ABEntry 构造函数内,theName,theAddress 和 thePhones 都不是被初始化,而是被赋值。初始化的发生时间更早,发生于这些成员的 default 构造函数被自动调用之时(比进入 ABEntry 构造函数本体的时间更早)。

因此,ABEntry 构造函数的更佳写法是使用成员初值列替换赋值动作:

ABEntry::ABEntry(const std::string& name, const std::string& address,
			const std::list<PhoneNumber>& phones)
        :theName(name), 	// 现在,这些都是初始化
        theAddress(address), 
        thePhones(phones), 
        numTimesConSulted(0) 
        {}	// 现在,构造函数本体不进行任何动作

改进后的构造函数和改进前的最终结果相同,但效率更高。因为,在改进后,theName 以 name 为初值进行拷贝构造;而改进前,先调用默认构造函数,然后再进行拷贝赋值操作。因此,只调用一次拷贝构造函数效率更高。

注意: 如果成员变量是 const 或 references,它们就一定需要初值,不能被赋值(见条款5)。因此,最简单的做法是:总是使用成员初值列!!!

跨编译单元之初始化次序

编译单元: 指产出单一目标文件(single object file)的那些源码。基本上它是单一源码文件加上其所含入的头文件(#include files)。

问题: 如果某编译单元内的某个 non-local static 对象的初始化动作使用了另一编译单元内的某个 non-local static 对象,它所用到的这个对象可能尚未被初始化,因为 C++ 对“定义于不同编译单元内的 non-local static 对象”的初始化次序并无明确定义,因此程序会出问题。例如:

class FileSystem {
public:
	...
    std::size_t numDisks() const; // 众多成员函数之一
    ...
};
extern FileSystem tfs;	// 需要给其他编译单元使用的对象
class Directory {
public:
	Directory(params);
    ...
};
Directory::Directory(params)
{
	...
    	//使用其他编译单元的tfs对象,可能会出现tfs未被初始化的情况,导致出问题
    std::size_t disks = tfs.numDisks();
    ...
}
Directory tempDir(params); // 创建对象

该写法,要求 tfs 在 tempDir 之前被初始化,否则会出错!

解决方式: 采用单例模式

class FileSystem { ... };	// 同前面
//用该函数来替换tfs对象;它在FileSystem class中可能是个static
FileSystem& tfs() 
{
	static FileSystem fs; // 定义并初始化一个local static对象
    return fs; // 返回一个reference指向上述对象
}
class Directory { ... }; // 同前面
Directory::Directory(params)
{
	...
    std::size_t disks = tfs().numDisks();
    ...
}
//用该函数来替换tempDir对象;它在Directory class中可能是个static
Directory& tempDir() 
{
	static Directory td;
    return td;
}

这么修改之后,这个程序可以像以前一样地使用,唯一不同的是他们现在使用 tfs() 和 tempDir() 而不再是 tfs 和 tempDir。也就是说他们使用函数返回的“指向 static 对象”的 references,而不再使用 static 对象自身。

注意: 从另一个角度看,这些函数“内含 static 对象”的事实使它们在多线程系统中带有不确定性。再说一次,任何一种 non-const static 对象,不论它是 local 或 non-local,在多线程环境下“等待某事发生”都会有麻烦。处理这个麻烦的一种做法是:在程序的单线程启动阶段(single-threaded startup portion)手工调用所有 reference-returning 函数,这可消除与初始化有关的“竞速形势(race conditions)”。

构造/析构/赋值运算

几乎你写的每一个 class 都会有一或多个构造函数、一个析构函数、一个拷贝赋值操作符。

0

评论区