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

目录

  • 1. 总结
  • 2. 构造函数体 VS 初始化列表
  • 3. 对象的初始化顺序问题

1. 总结

  • 无论是在初始化列表中,还是在构造函数体内,请为内置类型对象进行手工初始化,因为C++不保证初始化它们
  • 最好使用初始化列表进行初始化,而不要在构造函数体中使用赋值;初始化列表最好列出所有的成员变量,其排列顺序应该和它们在class中的声明顺序相同
  • 为了避免"不同源文件内定义的non-local static对象在编译时的初始化顺序"问题,请以local static对象替换non-local static对象

2. 构造函数体 VS 初始化列表

在C++中,关于对象的初始化动作何时一定发生,何时不一定发生这个问题,最佳的处理办法就是:永远在使用对象之前先将它初始化。

  • 对于内置类型对象,由于C++不保证是否初始化以及何时初始化它们,因此无论是在初始化列表中,还是在构造函数体内,你必须手工完成这项工作
  • 对于自定义类型对象,初始化工作由构造函数进行,规则也很简单:确保每一个构造函数都将对象的每一个成员初始化

关于在构造函数中初始化,重要的一点是不要混淆了赋值和初始化。

class PhoneNumber { ... };
class ABEntry
{
private:
    std::string theName;
    std::string theAddress;
    std::list<PhoneNumber> thePhones;
    int numTimesConsulted;
public:
    ABEntry{const std::string &name, const std::string &address,
            const std::list<PhoneNumber> &phones};
};

/* 正确可行但不是最好的方法:在构造函数体内对成员变量进行赋值 */
ABEntry::ABEntry{const std::string &name, const std::string &address,
                 const std::list<PhoneNumber> &phones}
{
    theName = name;         //theName、theAddress、thePhones都是赋值,
    theAddress = address;   //而不是初始化。
    thePhones = phones;
    numTimesConsulted = 0;
}

/* 较好的方法:使用初始化列表对成员变量进行初始化 */
ABEntry::ABEntry{const std::string &name, const std::string &address,
                 const std::list<PhoneNumber> &phones}
        :theName(name),
         theAddress(address),
         thePhones(phones),
         numTimesConsulted(0)  //为了一致性,内置类型对象初始化最好也在初始化列表中进行
{

}
  • 第一种方法基于赋值,首先调用default构造函数对theName、theAddress和thePhones进行初始化,然后进入构造函数,再分别对它们进行赋值。
  • 第二种方法基于初始化列表,在初始化列表中使用3个参数分别对theName、theAddress和thePhones进行copy构造初始化。

对大多数类型而言,比起先调用default构造函数然后再调用operator =,单只调用一次copy构造函数是比较高效的,有时甚至高效得多。
而对于内置类型对象如numTimesConsulted,其初始化和赋值的成本是一样的,但为了一致性最好也通过初始化列表来初始化。

如果成员变量是const或reference,那么不管是内置类型还是自定义类型,都一定需要初值,不能被赋值,都只能通过初始化列表进行初始化(见条款5)。
为避免需要记住何时必须使用初始化列表,何时不需要,最简单的做法就是:

  • 总是使用初始化列表
  • 总是在初始化列表中列出所有成员变量
  • 初始化列表中成员变量的排列顺序应该和它们在class中的声明顺序相同

但是,一些class有多个构造函数,而且有许多成员变量和/或base class,如果每个构造函数都使用初始化列表,那么就会造成大量的代码重复。
这种情况下,可以合理地对"赋值和初始化开销一样"的成员变量改用赋值操作,并将这些赋值操作封装到一个private init函数中,供所有构造函数调用。
这种做法在"成员变量的初始值来自于文件或数据库读入"时特别有用。然而,比起经由赋值操作完成的"伪初始化",通过初始化列表完成的"真正初始化"通常更加可取。

3. 对象的初始化顺序问题

C++在单个对象创建时有着十分固定的成员初始化顺序,口诀就是"先父母,再客人,后自己"。

  • 先调用父类的构造函数
  • 再调用成员变量的构造函数,调用顺序与声明顺序相同
  • 最后调用类自身的构造函数

如果已经在初始化列表中对base class和所有成员变量进行了初始化,那就只剩下一个问题——"不同源文件内定义的non-local static对象"的初始化问题。
先来明确下概念,函数内定义的static对象称为local static对象,其他地方定义的static对象称为non-local static对象。
现在,我们关心的问题涉及至少两个源文件,每个源文件中都至少含有一个non-local static对象,因此可能发生如下问题。

  • 某个源文件中的non-local static对象初始化需要使用另一个源文件中的non-local static对象
  • 但另一个源文件内的non-local static对象可能尚未被初始化

产生该问题的原因是C++对不同源文件中的non-local static对象初始化顺序没有明确定义,幸运的是通过一个小小的设计便可完全消除该问题,唯一需要做的是:

  • 将每个non-local static对象放到自己的专属函数中,这些函数返回一个reference指向它所含的对象
  • 然后用户调用这些专属函数,而不直接使用这些对象

该方法实际上是用local static对象替换了non-local static对象,这也是Singleton模式的一个常见实现手法。该方法之所以管用,是因为:

  • C++保证函数内的local static对象会在该第一次调用该函数时被初始化
  • 如果你从未调用过这些函数,就不会引发构造和析构成本

可以看到,这种结构下的函数体往往十分简单固定:第一行定义并初始化一个local static对象,第二行返回一个引用指向它。
这使得它们非常适合实现为inline函数,尤其是需要被频繁调用的场合;但从另一个角度看,内含static对象也使得它们成为线程不安全函数。

class FileSystem { ... };

//static FileSystem tfs;  //FileSystem.cpp中定义的non-local static对象

//tfs的专属函数,用来替换tfs对象
FileSystem &tfs()
{
    static FileSystem fs;  //定义并初始化一个local static对象fs
    return fs;             //返回一个reference指向上述对象
}
class Directory { ... };

Directory::Directory()
{
    //...
    std::size_t disks = tfs().numDisks();
    //...
}

//static Directory tempDir;  //Directory.cpp中定义的non-local static对象,tempDir的初始化依赖于FileSystem.cpp中的tfs对象先初始化完成

//tempDir的专属函数,用来替换tempDir对象
Directory &tempDir()
{
    static Directory td;  //定义并初始化一个local static对象td
    return td;            //返回一个reference指向上述对象
}

原文地址:https://www.cnblogs.com/songhe364826110/p/12189804.html

时间: 2024-10-16 15:41:34

条款04:确定对象使用前已被初始化的相关文章

effective c++ 条款 04 (对象初始化)整理

确定对象使用前已被初始化 原则:不论是类的成员变量还是其他作用域的变量,使用前都要保证已被初始化(或者说赋值) 一.无任何成员的内置类型初始化 /*内置类型通过复制完成初始化*/ int x = 0; const char* a = "abc"; int a[2] = {0,0}; 二.STL容器初始化 STL容器关注容器大小,防止越界,初始化的工作不用关心 三.类成员变量初始化 参考:http://www.cnblogs.com/BlueTzar/articles/1223169.h

[effictive c++] 条款04 确定对象被使用前已被初始化

成员初始化 在c和c++ 中,使用为初始化的类型常常会引发不可预料的错误,从而使得我们要花费巨大的时间用于调试查找问题,所以确定对象被使用前已被初始化是个很好的习惯. 永远在使用之前对对象进行初始化.对于无任何成员的内置类型,你必须手工完成初始化操作.因为c++不保证初始化他们. 内置类型意外的其他东西,初始化责任落在构造函数身上.但要注意区分构造函数中的变量是赋值还是初始化.举个例子 class PhoneNumber{...}; class ABEntry{ public: ABEntry(

条款4:确定对象被使用前已被初始化(Make sure that objects are initialized before they&#39;re used)

其实 无论学何种语言 ,还是觉得要养成先声明后使用,先初始化再使用. 1.永远在使用对象之前先将其初始化. 内置类型: 必须手工完成. 内置类型以外的:使用构造函数完成.确保每一个构造函数都将对象的一个成员初始化. shit!!读到这,以前一直以为有些概念独自觉悟到的,大牛们也有这样的想法!!!! 2.区分开assignment & initialization(这个需要再看看,似乎以前的认知是错误的!!!!) initializatin:在default构造函数时才是对非内置类型做初始化. a

Effective C++ 条款四 确定对象被使用前已被初始化

1.对于某些array不保证其内容被初始化,而vector(来自STL)却有此保证. 2.永远在使用对象前初始化.对于无任何成员的内置类型,必须手工完成.      int x = 0;      const int * p = &x; 3.不要混淆赋值与初始化的区别.一般初始化在定义的时候一起进行.而赋值是在定义之后的动作.      比如说在某一个类中的构造函数中,函数的行为都是赋值操作,而非初始化操作.      一般来说,对象的成员变量的初始化动作发生在进入构造函数本体之前.所以,我们一

04——确定对象使用前被初始化

内置类型手动初始化 类类型-构造函数(确保构造函数将对象的每个成员都初始化) 类构造函数初始化列表与函数体内赋值的区别: 效率更高 函数内的赋值语句执行时先执行成员的默认构造函数,再执行copy assignment 初始化列表直接执行一次 copy构造函数 含有const成员.reference成员时需使用初始化列表 基类的初始化 成员的初始化次序: 先基类,够派生类 class成员的初始化次序为其声明次序 04--确定对象使用前被初始化

《Effective C++》——条款04:确定对象使用前已先被初始化

读取未初始化的值会导致不明确的行为.在某些平台上,仅仅只是读取未初始化的值,就可能让你的程序终止运行.更可能的情况是读入一些“半随机”bits,污染了正在进行读取动作的那个对象,最终导致不可预知的程序行为,以及许多令人不愉快的调试过程. 对于内置类型的对象手动初始化 对于内置类型以外的任何其他东西,初始化责任落在构造函数身上. 但要区别赋值和初始化.对象的成员变量的初始化动作发生在进入构造函数本体之前. 在上述代码中,theName,theAddress和thephones都不是被初始化,而是被

条款04 确定对象被使用前已先被初始化

一.概述 手工初始化内置类型对象 构造函数的成员初始化列表 static对象的初始化:包括non-local static和local static 二.细节 1. 你的构造函数使用了成员初始值列表了咩 不使用初始值列表: class A { public: A(const string &s, int i) { //下面都是赋值,而非初始化 s = name; score = i; } private: string name; int score; }; 使用初始值列表: class A {

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

考虑如下设计: 1 #include<iostream> 2 3 using namespace std; 4 5 class Entry 6 { 7 public: 8 Entry(const string& name, const string& address, const string& phone); 9 10 private: 11 string name; 12 string address; 13 string phone; 14 int nums; 1

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

记住:永远在使用对象前先将它初始化. 1.不要混淆赋值assignment和初始化initialization. ABEntry::ABEntry(const std::string& name,const std::string& address,const std::list<PhoneNumber>& phones)//版本1 { theName = name;//这些都是赋值而非初始化 theAddress = address; thePhones = phon