条款4:确定对象被使用前已经被初始化
1、编译器初始化变量
编译器对某些变量可以自动初始化,但有些不能,具体那些不深入探讨,但对没有初始化的对象使用会导致不可预测的行为。因此,自己设定一个规则,每声明一个对象,必须手动初始化。
2、类初始化
2、1类的初始化列表
①构造函数的两个阶段
构造函数的执行可以分成两个阶段,初始化阶段和计算阶段,初始化阶段先于计算阶段
初始化阶段
所有类类型(class type)的成员都会在初始化阶段初始化,即使该成员没有出现在构造函数的初始化列表中
计算阶段
一般用于执行构造函数体内的赋值操作。
初始化阶段
所有类类型(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对象。
②构造函数最好使用成员初值列(member initialization list),而不要在构造函数本体内使用赋值操作(assignment)。初值列列出的成员变量,其排列次序应该和它们在class中的声明次序相同。
③为免除"跨编译单元之初始化次序"问题,请以local static对象替换non-local static对象。