Effective C++条款04笔记

“确定对象被使用前已被初始化"

          我们要分清楚赋值和初始化,正常情况下的构造函数大家是这样写的:

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对象。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值