“确定对象被使用前已被初始化"
我们要分清楚赋值和初始化,正常情况下的构造函数大家是这样写的:
test03(int age, boy& b, int cons, int& yingyong) {
c_age = age;
c_bot = boy;
c_cons = cons;
c_yingyong = yingyong;
}
这样写出来的并不是初始化,而是赋值。初始化时发生在赋值之前的操作。所以如果采用初始化列表就不会出现这种情况,以下是初始化列表版本。
初始化列表
语法:类名(形参列表):成员变量1(数值1),成员变量2(数值2),成员变量3(数值3)
test03(int age, boy &b, int cons,int& yingyong) :t_age(age-10), t_boy(b), t_cons(cons),t_yinyong(yingyong)
{
yingyong += 100;
cout << "调用了test03的全参数引用" << endl;
}
以下两种情况必须使用初始化列表进行初始化
1.类中的类成员变量,使用初始化列表可以提升性能
class boy
{
public:
string b_name;
}
class test03 {
public:
//!类中的类成员变量,使用初始化列表可以提升性能
boy t_boy;
test03(boy boy):t_boy(boy){
cout<<"构造函数"<<endl;
}
}
2.类的引用成员变量,也是需要初始化,必须使用初始化列表的话就不需要进行提前赋值了
class test03 {
public:
//!此为类的引用成员变量,也是需要初始化,必须使用初始化列表的话就不需要进行提前赋值了
int& t_yinyong;
test03(int& yingyong):t_yingyong(yingyong){
cout<<"构造函数"<<endl;
}
}
3类的常量成员变量,必须先初始化,必须使用初始化列表的话就不需要进行提前赋值了---- 不常用
class test03 {
public:
//此为类的常量成员变量,必须先初始化,必须使用初始化列表的话就不需要进行提前赋值了---- 不常用
const int t_cons;
test03(int cons):t_cons(cons){
cout<<"构造函数"<<endl;
}
}
- 如果成员是类,使用初始化列表性能会有提升
- 如果成员变量是引用或者常量,则必须要使用初始化列表
- 初始化列表和赋值有实质的区别,如果成员是类的话,使用初始化列表调用的是成员类的拷贝函数,而赋值是先创建类的对象
- 调用的是类的普通构造函数,然后在赋值
不同编译单元内定义的non-local static对象的初始化次序(摘抄自亭墨)
我们再看下一个话题之前先俩看几个概念:
1、static对象,其寿命从被构造出来知道程序结束位置,因此stack和heap-based对象都被排除。这种对象包括gloable对象,定义域namespace作用域内的对象,在classes内 ,在函数内,在file作用域中被声明为static的对象。在函数内的static对象称为local static对象(对于自己的函数是本地人),其他的static都是non-local的(其他的都是外人)。程序结束时static对象会被自动销毁(其析构函数会在main结束时被自动调用)
2、编译单元,指的是产出单一目标文件的那些源码。包括单一源码文件加上其所含如的头文件。
由于C++对于定义与不同编译单元内的non-local static对象的初始化次序并没有明确定义,假设我们现在有两个源码文件,每一个内含至少一个non-local static对象(该对象时global或者 定义域namespace作用域内抑或是在class内或file作用域内被声明为static)。我们在某个编译单元内的某个non-local static对象的初始化动作使用了另一编译单元内的某个non-local static 对象,使用到的对象可能并没有被初始化。
class FileSystem
{
public:
...
std::size_t numDisks() const;
...
};
extern FileSystem tfs; //extern会在之后说明
现在有个程序员建立了一个class用以 处理文件系统内的目录,用上FileSystem对象:
class Directory
{
public:
Directory(params);
...
};
Directory::Directory(params)
{
...
std::size_t disks = tfs.numDisks(); //使用tfs对象
...
}
现在创建一个Directory对象,用来放置临时文件:
Directory tempDir(params);
现在问题来了:除非tfs在tempDir之前先被初始化,否则tempDir的构造函数会用到尚未初始化的tfs。但tfs和tmpDir是不同人在不同时间于不同源码文件中创建的,他们是定义于不同编译单元内的non-local static对象。你是无法确定他们的初始化顺序的,原因在于:决定他们呢的初始化次序相当困难,根本无解。那么就完全没有办法了吗?并不是,他不是不是local static对象吗?那我给他个户口,让他变成本地人不就好了。换句话说就是non-local static对象被local static对象给替换了。这就是Singleton模式 常用的一个手法,其精髓在于函数内的local static对象会在该函数被调用期间首次遇上该对象的定义式时被初始化。所以如果你用一个返回引用的函数(引用指向local static对象)替换直接访问non-local static对象,你就获得了保障。更棒的是,如果你从未调用non-local static对象的“仿真函数”就绝不会引发构造和析构成本!修改后如下:
class FileSystem
{
public:
...
std::size_t numDisks() const;
...
}; //同上面
FileSystem& tfs() //这个函数用来替换tfs对象;它在FileSystemclass中
{ //可能是一个static,定义并初始化一个local static对象
static FileSystem fs; //返回一个reference指向该对象
return fs;
}
class Directory
{
public:
Directory(params);
...
}; //同上面
Directory::Directory(params)
{
...
std::size_t disks = tfs().numDisks(); //使用tfs对象
...
}
Directory& tempDir() //替换tempDir对象
{ //它在Directory class中可能是个static
static Directory td; //定义并初始化local static对象
return td; //返回一个reference指向上述对象
}
我们用tfs()和tempDir()代替最初的tfs和tempDir,也就是说我们使用的是指向对象的static对象的reference而不再是static本身。
这些函数内含“static”对象的事实使它们在多线程系统中带有不确定性。任何一种non-const static对象,不论其是否为local还是non-local,在多线程环境下等待某事发生都会有麻烦,处理这一麻烦的做法是:在程序的单线程启动阶段手工调用所有reference-returning函数,这可消除与初始化有关的竞速形势。
总结
- 为内置对象手动初始化,因为c++不保证初始化他们。
- 构造函数最好使用成员初值列,而不要在构造函数中使用赋值操作。初值列中成员的顺序,应该和他们在class中声明的顺序一样。
- 为避免“跨编译单元值初始化次序”问题,请以local static对象代替non-local static对象。