Effective c++ 学习笔记——条款04:确定对象被使用前已先被初始化

来源:互联网 发布:淘宝达人申请认证理由 编辑:程序博客网 时间:2024/05/29 09:55

Make sure that objects are initialized before they're used.

     昨天就已经把第四条款看完了,初始化这篇内容非常非常丰富,讲解了很多,也让我的一些疑惑解开了。由于经常加班,写作时间比较少,我今天看写完这篇文章,还要看下一条目,所以,我今天不能逐句的进行了解析,只能我觉得我认为比较重要的几点点出来。也希望大家给点指正。

变量的初始化

关于"将对象初始化"这事,C++ 似乎反复无常。如果你这么写:

    int i;

他会输出什么呢?给大家一段代码

// Initia_four.cpp : 定义控制台应用程序的入口点。//#include "stdafx.h"#include <iostream>using namespace std;int _tmain(int argc, _TCHAR* argv[]){    int i;    cout<<i<<endl;    system("pause");    return 0;}


 

大家看到了吧,打印的是这种东西,所以,你一定要记住,对变量一定要初始化,当你忘记初始化然后又使用了它,就会有意想不到的灾难产生。

构造函数初始化

    对于内置类型以外的其它对象,通常初始化责任落在构造函数身上。规则很简单:确保每一个构造函数都将对象的每一个成员初始化。但是必须清楚一个概念,就是赋值并非初始化。看下面的代码,假设有类型Person,它的构造函数所做的事情是赋值。虽然Person对象做到了你所期望的结果,但这不是C++中的最佳做法。

class Person {public:Person(string n, unsigned int a) {name = n;age = a;}private:string name;unsigned int age;};


        初始化列表的性能要高于赋值操作,因为对于string类型的name,它必须先调用string的default构造函数,然后再赋予新的值(copy assignment),这样default构造函数所做的事情全部浪费。不过对于内放类型来说,赋值与初始化列表的性能差异几乎没有。

所以,构造函数初始化最好用以下方式进行:

class Person {public:Person(string n, unsigned int a) :name(n),age(a){}private:string name;unsigned int age;};


 

       这样就很高效率了。

        许多classes拥有多个构造函数,每个构造函数有自己的成员初值列。如果这种classes存在许多成员变量和/或base classes,多份成员初值列的存在就会导致不受欢迎的重复(在初值列内)和无聊的工作(对程序员而言)。这种情况下可以合理地在初值列中遗漏那些"赋值表现像初始化一样好"的成员变量,改用它们的赋值操作,并将那些赋值操作移往某个函数(通常是private),供所有构造函数调用。这种做法在"成员变量的初值系由文件或数据库读入"时特别有用。然而,比起经由赋值操作完成的"伪初始化"(pseudo-initialization),通过成员初值列(member initialization list)完成的"真正初始化"通常更加可取。还有重要的一点就是:如果成员变量是const或者refrences,他们就一定需要初值,不能被赋值。


        必需要提到的是初始化列表的初始顺序不是根据在构造函数中写的顺序初始化,而是根据成员变量的声明顺序进行构造如下程序编译是不成功的

// Initia_four.cpp : 定义控制台应用程序的入口点。//#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;}

Singleton——单例模式

        所谓static对象,其寿命从被构造出来直到程序结束为止,因此stack和heap-based对象都被排除。这种对象包括global对象、定义于namespace作用域内的对象、在classes内、在函数内、以及在file作用域内被声明为static的对象。函数内的static对象称为local static对象(因为它们对函数而言是local),其他static对象称为non-local static对象。程序结束时static对象会被自动销毁,也就是它们的析构函数会在main()结束时被自动调用。

假设出现就是static变量,这些static变量分别定义在不同的文件内。最后编译器怎么来根据优先顺序编译呢。而如果出现某个变量的初始化依赖与另一个文件内的变量。为了解决这样的问题,最简单的方式就是使用Singleton(单例模式),使non-local static成为local static

如下代码实现了单例模式:

// Initia_four.cpp : 定义控制台应用程序的入口点。//#include "stdafx.h"#include <iostream>using namespace std;class FileSystem{};//同前FileSystem &tfs()//这个函数用来替换tfs对象;他在FileSystem中可能是个static。{    static FileSystem fs;    return fs;}class Directory{};Directory::Directory(){    std::size_t disks = tfs().numDisks();}Directory &tempDir(){    static Directory td;    return td;}int _tmain(int argc, _TCHAR* argv[]) {        return 0;}


但是从另一个角度看,这些函数"内含static对象"的事实使它们在多线程系统中带有不确定性。再说一次,任何一种non-const static 对象,不论它是local或non-local,在多线程环境下"等待某事发生"都会有麻烦。处理这个麻烦的一种做法是:在程序的单线程启动阶段(single-threaded startup portion)手工调用所有reference-returning函数,这可消除与初始化有关的"竞速形势(race conditions)"。

 

请记住

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


 

原创粉丝点击