本博客(http://blog.csdn.net/livelylittlefish )贴出作者(三二一@小鱼)相关研究、学习内容所做的笔记,欢迎广大朋友指正!
1. 永远在使用对象之前先将它初始化
(1) 对于无任何成员的内置类型,须手工进行初始化。
如:
int x = 0;
Const char* text = "A C-style string";
Double d;
Std::cin >> d; //以读取input stream的方式完成初始化
(2) 对于内置类型意外的任何其他东西,初始化由构造函数函数完成。
规则1:确保每一个构造函数都将对象的每一个成员初始化。
注意:别混淆了赋值(assignment)和初始化(initialization)。
如:
class 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> thePhones;
int numTimesConsulted;
};
ABEntry::ABEntry(const std::string& name, const std::string& address,
const std::list<PhoneNumber>& phones)
{
//all these are assignments, not initializations
theName = name;
theAddress = address;
thePhones = phones;
numTimesConsulted = 0;
}
C++规定:对象的成员变量的初始化动作发生在进入构造函数本体之前。
初始化的发生时间是这些成员的default构造函数被自动调用之时。
以上例子中,ABEntry的构造函数内,ABEntry类的成员变量都不是被初始化,而是被赋值。他们的初始化在进入ABEntry构造函数本体之前。
对于内置类型的numTimesConsulted不为真,内置类型不保证一定在我们看到的那个赋值动作之前获得初值。
推荐使用成员初始化列表(member initialization list)。
ABEntry::ABEntry(const std::string& name, const std::string& address,
const std::list<PhoneNumber>& phones)
:theName(name), //now, all these are initializations
theAddress(address),
thePhones(phones),
numTimesConsulted(0)
{ //now, constructor do nothing
}
该版本和上一个赋值的版本效果相同,但该版本的效率高。原因如下:
- 基于赋值的版本首先调用default构造函数为成员变量设初值,然后立刻在对他们赋予新值。
- default构造函数的一切作为因此浪费了。
- 成员初始化列表避免了这一问题。
若想要default构造一个成员变量,也可以使用成员初始值列表,只要制定nothing做为初始化实参即可。如:
ABEntry::ABEntry()
:theName(), //call default constructor of theName
theAddress(), //do the same thing for theAddress
thePhones(), //do the same thing for thePhones
numTimesConsulted(0) explicit initialize the build-in type member 0
{
}
规则2:在成员初始化列表中列出所有成员变量。
规则3:总是使用成员初始化列表。
有些情况下即使面对的成员变量属于内置类型(那么其初始化与赋值的成本相同),也一定要使用初始化列表。
例如,如果成员变量是const或者references,他们就一定需要出示,不能被赋值。
为避免需要记住成员变量何时必须在成员初始化列表中初始化,何时不需要,最简单的做法就是使用规则3。
2. 成员初始化次序
成员初始化次序:
- Base class
- Derived class
class的成员变量总是以其声明次序被初始化。即使他们在成员初始化列表中以不同的次序出现,也不会有任何影响。
static对象包括:
- global对象
- 定义于namespace作用域内的对象
- class内、函数内、在file作用域内被声明为static的对象
- Local static对象:函数内的static对象
- Non-local static对象,即其他static对象,如
- global对象
- namespace作用域内的对象
- class内或file作用域内被声明为static的对象。
程序结束时static对象会被自动销毁,即他们的析构函数会在main()结束时被自动调用。
编译单元(translation unit):是指产出单一目标文件(single object file)的那些源码。基本上它是单一源码文件加上其所含入的头文件(#include files)。
C++对“定义与不同编译单元内的non-local static对象”的初始化顺序并无明确定义。
如:在一个编译单元中定义FileSystem类
class FileStystem
{
public:
...
std::size_t numDisks() const;
...
};
extern FileSystem tfs; //the object reserved for users to use
在另一个编译单元中定义Directory类
class Directory
{
Public:
Directory(params);
...
};
Directory::Directory(params)
{
...
std::size_t disks = tfs.numDisks(); //use tfs object
...
}
Directory tempDir(params); //create temporary directory
在该例子中,tfs必须在调用Directory的构造函数之前初始化,否则tempDir的构造函数会用到尚未初始化的tfs。
但tfs和tempDir是不同的人在不同的时间于不同的源码文件建立起来的,他们是定义于不同编译单元内的non-local static对象。那么,如何确定tfs会在tempDir之前先被初始化?
答案是无法确定。
可通过如下设计——Singleton模式——解决这个问题:
- 将每个non-local static对象搬到自己的专属函数内(该对象在此函数内被声明为static)。这些函数返回一个reference指向它所含的对象。
- 然后用户调用这些函数,而不直接指涉这些对象。
- 即non-local static对象被local static对象替换了。
该方法的基础:C++保证,函数内的local static对象会在该函数被调用期间首次遇上该对象之定义式时被初始化。
使用该方法解决上述例子,如下。
class FileStystem
{
public:
...
std::size_t numDisks() const;
...
};
//this function used to replace tfs object, it can be static in FileSystem
FileSystem& tfs()
{
static FileSystem fs; //define and initialize a local static object
return fs; //return a reference to this object
}
class Directory
{
Public:
Directory(params);
...
};
Directory::Directory(params) //now, reference to tfs is replaced with tfs()
{
...
std::size_t disks = tfs().numDisks(); //use tfs function
...
}
Directory& tempDir() //this function used to replace tempDir object
{
static Directory td; //define and initialize a local static object
return td; //return a reference to this object
}
修改后,这个系统程序的客户完全像以前一样使用它,唯一不同的是使用tfs()和tempDir()代替tfs和tempDir对象。即使用函数返回的指向static对象的reference,而不再使用static对象本身。
任何一种non-const static对象,不论他是local还是non-local,在多线程环境下等待某事发生都会有麻烦。
处理该麻烦的做法:在程序的单线程启动阶段(single-threaded startup portion)手工调用所有reference-returing函数,可消除与初始化有关的race conditions。(具体参考其他资料)
总结:为避免在对象初始化之前过早地使用他们,要做
- 手工初始化内置型non-member对象
- 使用成员初始化列表(member initialization lists)对付对象的所有成分
- 在初始化次序不确定性(这对不同编译单元所定义的non-local static对象是一种折磨)情况下加强你的设计
Rember
- 为内置型对象进行手工初始化,因为C++不保证初始化他们。
- 构造函数最好使用成员初始化列表,而不要在构造函数本体内使用赋值操作(assignment)。初始化列表中列出的成员变量,其排列次序应该和他们在class中的声明次序相同。
- 为免除跨编译单元之初始化次序问题,以local static对象替换non-local static对象。