Effective C++条款04:让自己习惯C++之(确定对象被使用前已被初始化)

一、未初始化的危害

  • 使用未初始化的值会导致不明确的行为

二、内置类型的初始化

  • 对于内置的数据类型(char、int、float、double等),在使用前必须进行初始化,如果不初始化在使用时会出错,并且如果使用了未初始化的内置类型变量,有的编译会出错
int a;     //未初始化。危险

int x = 0; //初始化
const char* text = "Hello World";//初始化

double d;
std::cin >> d; //以读取输入流的方式完成初始化

三、构造函数

  • 对于class来说,在使用对象之前,必须使用构造函数对成员变量进行初始化
  • 但是需要注意赋值和初始化的区别

构造函数中的赋值操作(非初始化)

  • 例如下面的构造函数中,对成员变量的都不是初始化,而是赋值操作
class PhontNumber {};
class ABEntry
{
public:
    ABEntry(const std::string& name, const std::string& address,
        const std::list<PhontNumber>& phones);
private:
    std::string theName;
    std::string theAddress;
    std::list<PhontNumber> thePhones;
    int numTimesConsulted;
};

ABEntry::ABEntry(const std::string& name, const std::string& address,
    const std::list<PhontNumber>& phones)
{
    //下面的都是赋值操作,而不是初始化
    theName = name;
    theAddress = address;
    thePhones = phones;
    numTimesConsulted = 0;
}

成员初始化列表(真正的成员变量初始化)

  • 如果想要对成员进行初始化,应该是在构造函数的成员初始化列表对成员数据进行初始化
class PhontNumber {};
class ABEntry
{
public:
    //成员初始化列表初始化:真正的初始化
    ABEntry(const std::string& name, const std::string& address,
        const std::list<PhontNumber>& phones)
        :theName(name), theAddress(address), thePhones(phones), numTimesConsulted(0)
    {}
private:
    std::string theName;
    std::string theAddress;
    std::list<PhontNumber> thePhones;
    int numTimesConsulted;
};

四、为什么成员初始化列表比普通赋值效率高

  • 在构造函数内对数据成员赋值而言,效率更低,因为它们先需要调用class的默认构造函数对成员数据进行默认的初始化,然后用再调用各个成员的拷贝构造来进行复制。例如在上面的在构造函数内赋值的演示案例中,先调用ABEntry的默认构造函数对theName,theAddress,thePhones进行初始化,然后运行到构造函数内部的时候,再分别调用theName,theAddress,thePhones的拷贝构造函数进行拷贝
  • 成员初始化列表:成员初始化列表是在执行构造函数体前运行的代码,因此它们是真正的对成员数据进行初始化,从而避免了默认的构造函数调用以及拷贝构造等操作 

五、成员变量如果是内置类型

  • 成员变量如果是内置类型,即使没有进行初始化/赋值操作,那么编译器会自动为内置类型进行初始化,例如int被赋值为0等
  • 但是如果成员变量是const或引用,那么就必须进行初始化(不能在构造函数体内赋值,要在成员初始化列表中),而不是赋值
  • 总结:因此不论成员变量为什么类型,最好都必须进行初始化

六、成员初始化顺序

  • 成员初始化列表的初始化是有顺序的,越在前面的成员变量越先被初始化,在后面的成员变量后被初始化。(与成员变量定义的顺序无关)
  • 例如,上面介绍的构造函数中,theName先初始化,再接着是theAddress,thePhones,numTimesConsulted依次被初始化
ABEntry(const std::string& name, const std::string& address,
    const std::list<PhontNumber>& phones)
    :theName(name), theAddress(address), thePhones(phones), numTimesConsulted(0)
{}

七、静态对象初始化问题

  • 静态对象的生命周期从定义开始,直到程序结束为止
  • 静态成员分为哪几类?

    • 非局部的静态对象:全局静态对象、定义与namespace作用域内的对象、class的静态成员
    • 局部的静态对象:函数内部的局部静态变量
  • 本文主要探讨在不同编译单元之间定义“非局部的静态对象”的初始化顺序问题
  • 编译单元:是指产出单一目标文件的源码,通常是一个源文件加上一个头文件的组合

演示案例

  • 现在有一个FileSystem类,它类似于一个文件系统,供网络上的用户使用,其定义在FileSystem.h文件中。定义如下:
//FileSystem.h
class FileSystem
{
public:
    std::size_t numDisks()const;
};

extern FileSystem tfs;
  • 现在客户端程序在Directory.h中建立Directory类用来处理文件系统内的目录
//Directory.h

#include "FileSystem.h"
class Directory
{
public:
    Directory();
};

Directory::Directory()
{
    std::size_t disks = tfs.numDisks(); //使用tfs对象
}

//现在客户在主程序中创建一个Directory对象,用来放置临时文件
Directory tempDir;
  • 现在问题来了:如果要使用tempDir,必须确保tfs对象在这之前已经被初始化了,否则tempDir就会用到未初始化的tfs。但是tfs对象与tempDir对象是出于不同编译单元之间的对象,那怎样才能保证tfs在tempDir使用之前已经被初始化了呢?答案是没有这种方法可以确保

对于上述演示案例的解决办法

  • 幸运的是,有一个小小的设计就可以确保上面的这个问题。经需要用到的非局部静态对象封装到一个函数内,对象在函数内部被声明为static,然后函数返回一个指向这个对象的引用。用户之后调用这些函数,而不是直接调用这些对象。此处非局部静态对象被局部对象替换了。这在设计模式中,是Singleton模式的一种常见手法
  • 这种手法保证:函数内的局部静态对象会在“该函数被调用期间,第一次遇到该对象的定义时”时被初始化
  • 更改FileSystem.h
//FileSystem.h
class FileSystem
{
public:
    std::size_t numDisks()const;
};

FileSystem& tfs()
{
    static FileSystem tfs;
    return fs;
}
  •  更改Directory.h
//Directory.h

#include "FileSystem.h"
class Directory
{
public:
    Directory();
};

Directory::Directory()
{
    std::size_t disks = tfs.numDisks(); //使用tfs对象
}

//这个函数用来替换tempDir对象,它在Directory内部可能是static
Directory& tempDir()
{
    static Directory td;
    return td;
}

七、总结

  • 为内置型对象进行弓手初始化,因为C++不保证初始化它们
  • 构造函数最好使用成员初始化列表,而不要再构造函数体内部做赋值操作
  • 为避免“跨编译单元之初始化次序”问题,请以局部静态对象替换非局部静态对象
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

董哥的黑板报

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值