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

本文详细阐述了未初始化值的危害,内置类型初始化的必要性,构造函数中成员初始化列表的重要性及其效率优势。通过实例讲解了如何避免跨编译单元的初始化顺序问题,以及如何正确使用静态对象。核心内容包括初始化规范、构造函数实践和Singleton设计模式应用。
摘要由CSDN通过智能技术生成

一、未初始化的危害

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

二、内置类型的初始化

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

  2.  
  3. int x = 0; //初始化

  4. const char* text = "Hello World";//初始化

  5.  
  6. double d;

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

三、构造函数

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

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

  • 例如下面的构造函数中,对成员变量的都不是初始化,而是赋值操作
 
  1. class PhontNumber {};

  2. class ABEntry

  3. {

  4. public:

  5. ABEntry(const std::string& name, const std::string& address,

  6. const std::list<PhontNumber>& phones);

  7. private:

  8. std::string theName;

  9. std::string theAddress;

  10. std::list<PhontNumber> thePhones;

  11. int numTimesConsulted;

  12. };

  13.  
  14. ABEntry::ABEntry(const std::string& name, const std::string& address,

  15. const std::list<PhontNumber>& phones)

  16. {

  17. //下面的都是赋值操作,而不是初始化

  18. theName = name;

  19. theAddress = address;

  20. thePhones = phones;

  21. numTimesConsulted = 0;

  22. }

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

  • 如果想要对成员进行初始化,应该是在构造函数的成员初始化列表对成员数据进行初始化
 
  1. class PhontNumber {};

  2. class ABEntry

  3. {

  4. public:

  5. //成员初始化列表初始化:真正的初始化

  6. ABEntry(const std::string& name, const std::string& address,

  7. const std::list<PhontNumber>& phones)

  8. :theName(name), theAddress(address), thePhones(phones), numTimesConsulted(0)

  9. {}

  10. private:

  11. std::string theName;

  12. std::string theAddress;

  13. std::list<PhontNumber> thePhones;

  14. int numTimesConsulted;

  15. };

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

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

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

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

六、成员初始化顺序

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

  2. const std::list<PhontNumber>& phones)

  3. :theName(name), theAddress(address), thePhones(phones), numTimesConsulted(0)

  4. {}

七、静态对象初始化问题

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

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

演示案例

  • 现在有一个FileSystem类,它类似于一个文件系统,供网络上的用户使用,其定义在FileSystem.h文件中。定义如下:
 
  1. //FileSystem.h

  2. class FileSystem

  3. {

  4. public:

  5. std::size_t numDisks()const;

  6. };

  7.  
  8. extern FileSystem tfs;

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

  2.  
  3. #include "FileSystem.h"

  4. class Directory

  5. {

  6. public:

  7. Directory();

  8. };

  9.  
  10. Directory::Directory()

  11. {

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

  13. }

  14.  
  15. //现在客户在主程序中创建一个Directory对象,用来放置临时文件

  16. Directory tempDir;

  • 现在问题来了:如果要使用tempDir,必须确保tfs对象在这之前已经被初始化了,否则tempDir就会用到未初始化的tfs。但是tfs对象与tempDir对象是出于不同编译单元之间的对象,那怎样才能保证tfs在tempDir使用之前已经被初始化了呢?答案是没有这种方法可以确保

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

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

  2. class FileSystem

  3. {

  4. public:

  5. std::size_t numDisks()const;

  6. };

  7.  
  8. FileSystem& tfs()

  9. {

  10. static FileSystem tfs;

  11. return fs;

  12. }

  •  更改Directory.h
 
  1. //Directory.h

  2.  
  3. #include "FileSystem.h"

  4. class Directory

  5. {

  6. public:

  7. Directory();

  8. };

  9.  
  10. Directory::Directory()

  11. {

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

  13. }

  14.  
  15. //这个函数用来替换tempDir对象,它在Directory内部可能是static

  16. Directory& tempDir()

  17. {

  18. static Directory td;

  19. return td;

  20. }

七、总结

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值