Item 04:确定目标吃使用前都事先被初始化

Item 04: Make sure that objects are initialized before they’re used


读取未初始化的价会招未显眼的行事。而最佳的拍卖措施就是是:世代当利用对象之前先行用她初始化

对于停放类型,你不能不手工完成此事。

对于放开类型以外的别其它东西,初始化责任落于构造函数身上。规则不行简答:保各级一个构造函数都拿对象的各个一个分子初始化

赋值和初始化

“确保各一个构造函数都用目标的各国一个成员初始化”看起格外轻奉行,重要之是生成模糊了赋值和初始化。

class PhoneNumber { ... };
class ABEntry {                 // ABEntry = “Address Book Entry”
public:
    ABEntry(const std::string& name, const std::string& address,
            const std::list<PhoneNumber>& phones);
private:
    std::string theName;
    std::string theAddress;
    std::list<PhoneNumber> thePhones;
    int numTimesConsulted;
};
ABEntry::ABEntry(const std::string& name, const std::string& address,
                 const std::list<PhoneNumber>& phones)
{
    theName = name;       // 这些都是赋值,
    theAddress = address; // 而非初始化
    thePhones = phones;
    numTimesConsulted = 0;
}

眼看会造成ABEntry对象涵盖你要的值,但不是顶尖做法。C++规定,目标的积极分子变量的初始化动作有在登构造函数本体之前。在ABEntry构造函数内,theName,theAddress和thePhones都无是受初始化,而是给赋值。初始化发生时再次早,发生受这些成员的默认构造函数被自动调用的常(比跻身ABEntry构造函数本体的日再早)。

ABEntry构造函数的较好之写法是,使用所谓的分子初始化列表轮换赋值动作:

ABEntry::ABEntry(const std::string& name, const std::string& address,
                 const std::list<PhoneNumber>& phones)
    : theName(name),
      theAddress(address),  // 这些都是初始化
      thePhones(phones),
      numTimesConsulted(0)
{}                            //构造函数本体不必有任何动作

以此构造函数和高达一个之末尾结果一致,但平常频率还胜似。基于赋值的深本首先调用默认构造函数为theName,theAddress和thePhones设初值,然后就还针对它与新值。默认构造函数的整作为因此让荒废了。成员初始化列表的做法避免了马上同样题目,因为初值列表中针对各个成员变量而设的实参,被将去当各级成员变量的构造函数的实参。本例中的theName以name为初值进行copy构造,theAddress以address为初值进行copy构造,thePhones以ponoes为初值进行copy构造。

本着多数路而言,比起先调用默认构造函数然后再度调用赋值操作符,仅仅调用一差copy构造函数是较高效之,有时还是很快之差不多。对于坐类型对象如numTimesConsulted,其初始化和赋值的资产平,但为了一致性最好吧由此成员初始化列表来初始化。

同等道理,甚至当您想只要默认构造一个分子变量,你还足以以成员列表初始化,只要指定无物作为初始化实参即可。加入ABEntry有一个不管参数构造函数:

ABEntry::ABEntry()
    : theName(),            //调用theName的默认构造函数;
      theAddress(),         //调用theAddress的默认构造函数;
      thePhones(),          //调用thePhones的默认构造函数;
      numTimesConsulted(0)  //显示初始化为0
{} 

由于编译器会为用户从定义类型的成员变量自动调用默认构造函数——如果那些成员变量在“成员初始化列表”中从来不给指定初值的口舌,因而引发某些程序员夸张地用上述写法。这是足以理解的,但是一定要是当成员初始化列表中列有富有成员变量,以免还得记住什么成员变量可以不必初值。例如,由于numTimesConsulted属于内置类型,如果成员初始化列表遗漏了它们,它就是从来不初值,因此可能会见敞开“不明白行为”的潘多拉盒子。

多少情况下,即使面对的积极分子变量属于内置类型(那么该初始化与赋值的财力平),也毫无疑问得动初始化类表。是的,如果分子变量是const或reference,它们就是肯定用初值,不能够为赋值。为避需记住成员变量何时要以成员初始化列表中初始化,何时无欲,最简便易行粗暴的做法是:一连利用成员初始化列表。这样做有时候绝对必要,而且还要频繁比赋值更迅速。

分子初始化次序

C++有一定的“成员初始化次序”。次序总是一样:base
classes更早同那dderived
classes被初始化,而class的积极分子变量总是因为那个声明次序被初始化
。看看ABEntry,其theName成员永远长给初始化,然后是theAddress,再来是thePhone,最后是numTimesConsulted。即使它在成员初始化列表中盖不同之程序出现(编译器会报发出警示),也未会见发出其它影响(只是报出警告)。为了避免代码阅读者的迷离,或者必须有涩错误(两独成员变量的初始化带有次序性,例如初始化数组时需要指定大小,因此代表大小的雅成员变量必须先行来初值),当你当成员初始化列表中列出各个成员时,最好总是因为那个宣称次序为序

每个成员以构造函数初始化列表中不得不指定同赖。构造函数初始化列表仅指定用于初始化成员的价值,并无点名这些初始化执行的次。成员给初始化的先后就是概念成员的主次
————《C++ Primer》第四版 P389

不等编译单元内定义之non-local static对象的初始化次序

若果您都坏小心地用“内置型成员变量”明确地加以初始化,而且也准保您的构造函数运用成员初始化列表初始化base
classes和成员变量,那就单剩余一宗事待担心了,就是————“不同编译单元内定义之non-local
static对象”的初始化次序。

所谓static对象

所谓static对象,其寿命由让组织出直到程序结束为止,因此stack和heap-based对象都吃辟。这种对象包括global对象、定义及namespace作用域内之对象、在classes内、在函数内、以及当file作用域内叫声称也static的靶子。函数内的static对象称为local
static对象,其他static对象称为non-local
static对象。程序结束时static对象见面让机关销毁,也就是它们的析构函数会在main()结束时吃自动调用。

所谓编译单元

所谓便一样单元是凭产出单一目标文件的那些源码。基本上它是纯净源码文件加上其所含入的峰文件。

本,我们关注的题目事关至少少单源码文件,每一个内含至少一个non-local
static对象(也就是说该对象是global或放在namespace作用域内,抑或在class内要file作用域内叫声称也static)。真正的题材是:如果某个编译单元内的某部non-local
static对象的初始化动作下了另外一个编译单元内的某个non-local
static对象,它所用到之这目标或无给初始化,因为C++对“定义不同编译单元内之non-local
static对象”的初始化次序并随便明显定义。

一经你闹一个FileSystem
class,它深受互联网上的文件看起好像置身本机。由于这class使世界看起如只纯文件系统,你可能会见并发一个异样目标,位于global或namespace作用域内,象征单一文件系统:

class FileSystem {               // from your library’s header file
public:
    ...
    std::size_t numDisks() const; // one of many member functions
    ...
};
extern FileSystem tfs;  // declare object for clients to use
                        // (“tfs” = “the file system” );
                        // definition is in some .cpp file in your library

本要某些客户建立了一个class用以处理文件系统内的目录。很自然它们的class会为此上tfs对象。

class Directory {         // created by library client
public:
    Directory( params );
    ...
};
Directory::Directory( params )
{
    ...
    std::size_t disks = tfs.numDisks(); // use the tfs object
    ...
}

愈使,这些客户决定创办一个Directory对象,用来放临时文件:

Directory tempDir( params );    //directory for temporary files

本初始化次序的重要性显现出来了:除非tfs在tempDir之前先给初始化,否则tempDir的构造函数会用到没初始化的tfs。但tfs和tempDir是殊的人以不同之时日为不同的源码文件建立起来的,它们是概念为不同编译单元内的non-local
static对象。如何会规定tfs会在tempDir之前先行叫初始化?

local static 替换 non-local static

幸运的是一个纤维设计好完全消除这题材。唯一需要举行的是:将每个non-local
static对象搬至温馨之专属函数内(该目标在这个函数内吃声称也static)。这些函数返回一个reference指向她所涵盖的靶子。然后用户调用这些函数,而未直指涉这些目标。换句话说,non-local
static对象为local
static对象替换了。这虽是Singleton模式(单例模式)的一个周边手段。

此手法的根底在于:C++保证,函数内之local
static对象会于“该函数被调用内”“首潮面临上该目标的定义式”时被初始化。所以一旦您为“函数调用”(返回一个reference指向local
static对象)替换“直接访问non-local
static对象”,你尽管取了保险,保证你所收获的杀reference将针对一个历经初始化的对象。更精的凡,如果你莫调用non-local
static对象的“仿真函数”,就不用会吸引构造与析构成本;真正的non-local
static对象可是不曾立顶利!

class FileSystem { ... };   // as before
FileSystem& tfs()           // this replaces the tfs object; it could be
{                           // static in the FileSystem class
    static FileSystem fs;   // define and initialize a local static object
    return fs;              // return a reference to it
}
class Directory { ... };        // as before
Directory::Directory( params )  // as before, except references to tfs are
{                               // now to tfs()
    ...
    std::size_t disks = tfs().numDisks();
    ...
}
Directory& tempDir()                // this replaces the tempDir object; it
{                                   // could be static in the Directory class
    static Directory td( params );  // define/initialize local static object
    return td;                      // return reference to it
}

这种布局下的reference-returning函数往往很但:第一执行定义并初始化一个local
static对象,第二实施返回她。这样的纯使它们叫绝佳的inline候选人,尤其要它被频繁调用的话语。但是自其他一个角度看,这些函数“内涵盖static对象”的实使它在多线程系统面临包含不明明。任何一样栽non-const
static对象,不论它是local或non-local,在差不多线程环境下“等待某事发生”都见面起劳动。处理者累的等同种植做法是:在次的单线程启动等手工调用所有reference-returning函数,这可解除与初始化有关的“竞速形势”。

应用reference-returning函数防止“初始化次序问题”,前提是内部拥有一个针对目标而言合理之初始化次序。如果你闹一个体系,其中目标A必须以靶B之前先初始化,但A的初始化能否打响可以受制于B是否都初始化,这时候若就生劳动了。坦白说你自作自受。只要避开如此病态的光景,此处描述的方应该好供您优质的劳务,至少在单线程程序中。

Note

  • 呢内置型对象进行手工初始化,因为C++不包初始化它们。
  • 构造函数最好应用成员初始化列表,而不要以构造函数本体内采用赋值操作。初始化列表中列有之分子变量,其列顺序应该同她于class中的宣示次序相同
  • 也解“跨编译单元的新始化次序”问题,请以local
    static对象替换non-local static对象。

Effective C++