条款4:确定对象被使用前已经被初始化

条款4:确定对象被使用前已经被初始化

1、编译器初始化变量

    编译器对某些变量可以自动初始化,但有些不能,具体那些不深入探讨,但对没有初始化的对象使用会导致不可预测的行为。因此,自己设定一个规则,每声明一个对象,必须手动初始化。

2、类初始化

2、1类的初始化列表

①构造函数的两个阶段 
构造函数的执行可以分成两个阶段,初始化阶段和计算阶段,初始化阶段先于计算阶段
初始化阶段
    所有类类型(class type)的成员都会在初始化阶段初始化,即使该成员没有出现在构造函数的初始化列表中
计算阶段
    一般用于执行构造函数体内的赋值操作。

②采用非初始化列表
看段代码
	class Student
	{
	public:
		Student(const std::string name, const int id)
		{
			this->m_name = name;
			this->m_id = id;		
		}
	private:
		std::string m_name;
		int m_id;
	};

分析:该构造函数执行过程,在初始化阶段对m_name调用string缺省构造函数初始化,再调用string的赋值函数赋值,这样调用default构造函数成为多余操作,假如该类的成员加入结构体,类对象,而且很多,那么这是一笔不小的开销,不过对于内置类型的m_id,赋值与初始化列表无差异,但为了保证不遗漏任何的成员变量放入初始化列表,尽量做到每个成员都放入初始化列表 还有一种是如果成员变量是const或者refrences,他们就一定需要初值,不能被赋值
    因此我们直接采用构造函数初始化列表,即调用string的拷贝构造函数初始化,省去调用default构造函数的开销。
将构造函数修改为如下
		Student(const std::string name, const int id)
			: m_name(name)
			, m_id(id)
		{

		}
缺省参数构造函数可以初始化列表
		Student() : m_name("name"), m_id(5)  //无参数缺省构造函数初始化
		{

		}

如果一个类有几个构造函数,为了代码不冗余,根据初始化的成员函数是否决定使用初始化列表还是直接赋值,直接赋值法,可以定义一个private内部函数统一初始化成员。

2、2类初始化顺序

①基类初始化,根据继承基类的顺序初始化
②成员变量初始化,根据成员变量在类中声明的顺序,尽管成员变量在初始化列表中的顺序与声明不同,编译器还是根据声明顺序初始化,但为了可读性,在初始化列表中顺序与声明一样。
在网上找了一个代码
#include "stdafx.h"  
#include <iostream>  
using namespace std;  
template<typename T>  
class Array {  
public:  
    Array(size_t size)   
        : _size(size) {   
            _arr = new T[size];  
    }  
    ~Array() {  
        delete[] _arr;  
    }  
private:  
    size_t _size;  
    T *_arr;  
};  
template<typename T>  
class SafeArray {  
public:  
    SafeArray() : _size(5), _arr(_size) {   //注意声明顺序
    }  
private:  
    Array<T> _arr;  
    size_t _size;  
};  
int _tmain(int argc, _TCHAR* argv[]) {  
    SafeArray<int> sArr;  
    system("pause");  
    return 0;  
} 
分析:根据声明顺序_arr应该是先初始化,但_size还未初始化,因此_size的值取决编译器,有些编译器会直接给一个随机值,有些是程序崩溃。

2、3不同编译单元的non-local static 对象

编译单元
    指编译时产生单一目标文件的那些源码,基本上是一个cpp文件和该cpp文件包含的头文件
静态对象
    其寿命从被构造出来直到程序结束为止,因此stack和heap-based对象都被排除。这种对象包括global对象、定义于namespace作用域内的对象、在classes内、在函数内、以及在file作用域内被声明为static的对象。函数内的static对象称为local static对象(因为它们对函数而言是local),其他static对象称为non-local static对象。程序结束时static对象会被自动销毁,也就是它们的析构函数会在main()结束时被自动调用。
    如果某个编译单元的某个non-local static对象初始化需要用到另一个编译单元的non-local static对象,而这个对象也没用初始化,则结果不可知,因为C++对“不同编译单元的non-local static对象初始化顺序并无明确定义。
如何解决:通过将non-local static对象转变为local static对象,即通过一个专属函数,在该函数内定义该对象为local static,并返回引用。
	class FileSystem
	{
	public:
		void CreateFile(const string &fileName) 
		{
			//createFile
			cout<<"create "<<fileName<<endl;
		}
	};

	FileSystem & GetFileSystem()
	{
		static FileSystem fs;
		return fs;
	}

	class Directory
	{
	public:
		Directory(const string &fileName)
		{
			GetFileSystem().CreateFile(fileName);
		}
	};

记住:
①为内置型对象进行手工初始化,因为C++不保证初始化它们。
②构造函数最好使用成员初值列(member initialization list),而不要在构造函数本体内使用赋值操作(assignment)。初值列列出的成员变量,其排列次序应该和它们在class中的声明次序相同。
③为免除"跨编译单元之初始化次序"问题,请以local static对象替换non-local static对象。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值