确定对象被使用前已先被初始化,言简意赅。
因为如果直接使用未初始化的对象,初值未定,行为是不明确的,可能会让程序终止运行,或者发生不可预测的行为等。在c++中,可以通过该类的构造函数初始化该类所包含的所有成员变量。引用书中的说法:“确保每一个构造函数都将对象的每一个成员初始化”
需要注意的是在使用构造函数初始化对象时应当使用构造函数的初始化列表初始化成员,而不要是赋值运算,比如:
class MyType { public: MyType() { cout << "default constructor" << endl; } MyType(const MyType &) { cout << "copy constructor" << endl; } MyType& operator=(const MyType &myType) { cout << "operator = " << endl; return *this; } }; class Demo { public: Demo(const MyType &myType); private: MyType myType; }; //第一种初始化myType的方法 Demo::Demo(const MyType &myType) : myType(myType) { } //第二种初始化myType的方法 Demo::Demo(const MyType &myType) { this->myType = myType; } int main() { cout << "myType init begin" << endl; MyType myType; cout << "myType init end" << endl << endl; Demo demo(myType); }
在Demo类中有一个自定义的MyType类型的成员变量,在构造函数初始化时应当使用第一种初始化的方法,而不是第二种。
分别运行两种初始化的方法可以发现,第一种只调用了拷贝构造函数:
myType init begin default constructor myType init end copy constructor
而第二种首先使用MyType默认构造函数初始化成员变量myType,而后又调用了赋值运算赋初值:
myType init begin default constructor myType init end default constructor operator =
对于大多数类型而言,比起先调用default构造函数然后再调用copy assignment操作符,单只调用一次copy构造函数是比较高效的,有时甚至高效得多。对于内置类型如int
,其初始化和赋值的成本相同,但为了一致性最好也通过成员初值列来初始化。
如果成员变量是const或references,它们就一定需要初值,不能被赋值。为避免需要记住成员变量何时必须在成员初值列中初始化,何时不需要,最简单的做法就是:总是使用成员初值列。这样做有时候绝对必要,且又往往比赋值更高效。
C++有着十分固定的成员初始化次序。基类更早于派生类被初始化,类的成员变量总是以其声明的次序被初始化。
不论成员初值列是以什么顺序初始化成员变量,成员变量的初始化顺序都只与声明的顺序有关。
C++对“定义于不同编译单元内的non-local static对象”的初始化次序并无明确定义。
所谓编译单元是指产出单一目标文件(single object file)的那些源码。基本上它是单一源码文件加上其所含入的头文件。
函数内的static对象称为local static对象(因为它们对函数而言是local),其他static对象称为non-local static对象。
比如,有一个FileSystem class
class FileSystem{ public: ... std::size_t numDisks() const; ... }; extern FileSystem tfs;
当用户使用该类建立一个处理文件系统的目录类时,很自然的会用到tfs对象:
class Directory{ public: Directory(params); ... }; Directory::Directory(params) { ... std::size_t disks = tfs.numDisks(); ... }
但是,当创建Directory对象tempDir时:
Directory tempDir(params);
如何保证tfs一定会在tempDir之前被初始化?
tfs和tempDir是不同的人在不同的时间于不同的源码文件建立起来的。C++对这种定义于不同编译单元内的non-local static对象的初始化相对次序并无明确定义。
我们可以将每个non-local static对象搬到自己的专属函数内(该对象在此函数内被声明为static)。这些函数返回一个reference指向它所含的对象。然后用户调用这些函数,而不直接指涉这些对象。换句话说,non-local static对象被local static对象替换了。
C++保证,函数内的local static对象会在“该函数被调用期间” “首次遇上该对象之定义式”时被初始化。所以以“函数调用“(返回一个reference 指向local static对象)替换”直接访问non-local static对象“就获得了一个保证,保证所得的那个reference将指向一个历经初始化的对象。
就像这样:
class FileSystem {...}; FileSystem& tfs() { static FileSystem fs; return fs; } class Directory {...}; Directory::Directory(params) { ... std::size_t disks = tfs().numDisks(); ... } Directory& tempDir() { static Directory td; return td; }
这么修改后,这个系统程序的客户完全像以前一样使用它,唯一不同的是他们现在使用tfs()和tempDir()而不是tfs和tempDir。也就是说他们使用函数返回的”指向static对象“的references,而不再使用static对象自身。这样就可以防止使用跨编译单元的non-local static对象时,对象未初始化的问题。