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

  所谓编译单元(translation unit)是指出单一目标文件的那些源码。基本上它是单一源码文件加上其所含入的头文件(#include files)。

  现在,我们关心的问题涉及至少两个源码文件,每一个内含至少一个non-local static 对象。真正的问题是:如果某编译单元内的某个non-local static对象,它所用到的这个对象可能尚未被初始化,因为c++对“定义于不同编译单元的non-local static对象”的初始化次序并无明确定义。

  实例可以帮助理解。假设你有一个FileSystem class,它让互联网的文件看起来好像位于本机。由于这个class使世界看起来像个单一文件系统,你可能会产出一个特殊对象,位于global或namespace作用域内,象征单一文件系统:
class FileSystem {                       // 来自你的程序库
public:
  ...
  std::size_t numDisks() const; // 众多成员函数之一
} ;
extern FileSystem tfs;           // 预备给客户使用的对象;

  FileSystem对象绝不是一个稀松平常无关痛痒的(trival)对象,因此你的客户如果在theFileSystem对象够造完成前就使用它,会得到惨重的灾情。

  现在假设某些客户建立了一个class用以处理文件系统内的目录(directories)。很自然他们的class会用上theFileSystem对象:
class Directory {
public:
  ....
} ;
Directory::Directory( params)
{
  ...
  std::size_t disks = tfs.numDisks(); // 使用tfs对象
  ...
}

  进一步假设,这些客户决定创建一个Directory对象,用来放置临时文件:
Directory tempDir( params); // 为临时文件而做出的目录

  现在,初始化次序的重要性显现出来了:除非tfs在tempDir之前先被初始化,否则tempDir的构造函数会用到尚未初始化的tfs。但tfs和tempDir是不同的人在不同的时间于不同的源码文件建立起来的,他们是定义于不同编译单元内的non-local static对象。如何能够确定tfs会在tempDir之前被初始化?

  喔,你无法确定。再说一次,C++对“定义于不同编译单元内的non-local static对象”的初始化相对次序并无明确定义。这是有原因的:决定他们的初始化次序相当困难,非常困难,根本无解。在其最常见形式,也就是多个编译单元内的non-local static对象经由“模版隐式具现化,implicit template instantiations”形成 (而后者自己可能也是经由“模版隐式具现化”形成),不但不可能决定正确的初始化次序,甚至往往不值得寻找“可决定正确次序”的特殊情况。

  幸运的是一个小小的设计便可完全消除这个问题。唯一需要做的是:将每个non-local static 对象搬到自己专属函数内(该对象在此函数内被声明为static)。这些函数返回一个reference指向它所含的对象。然后用户调用这些函数,而不直接只设这些对象。换句话说,non-local static 对象被local static对象替换了。Design Patterns 迷哥迷妹们想必认出来了,这是Singleton模式的一个常见的实现手法。

  这个手法的基础在于:c++保证,函数内的local static 对象会在“该函数被调用期间“”首次遇上该对象之定义式”时被初始化。所以如果你“函数调用”(返回一个reference指向local static对象)替换“直接访问non-local static对象”,你就获得了保证,保证你所获得的那个reference将指向一个经理初始化的对象。更棒的是,如果你从未调用non-local static对象的“仿真函数”,就绝不会引发构造和析构成本:真正的non-local static 对象可没这等便宜!
  以此技术施行于tfs和tempDir身上,结果如下:
class FileSystem { ... };
FileSystem& tfs()
{
  static FileSystem fs;
  return fs;

class Directory { ... };
Directory::Directory( params )
{
  ...
  std::size_t disk = tfs().numDisks();
  ...

Directory& tempDir()
{
  static Directory td;
  return td;
}

  这么修改之后,这个系统程序的客户端完全像以前一样地用它,唯一不通过的是他们现在使用tfs和tempDir。也就是说他们使用函数返回的“指向static 对象”的references,而不是再使用static对象自身。

  这种结构下的reference-returning函数往往十分单纯:第一行定义并初始化一个local static 对象,第二行返回它。这样的单纯性使它们称为绝佳的inlining候选人,尤其如果它们被频繁调用的。但是从另一个角度看,这些函数“内含static对象”的事实使它们在多线程系统中带有不确定性。再说一次,任何一种non-const static对象,不论它是local或non-local,在多线程环境下“等待某事发生”都会有麻烦。处理这个麻烦的一种做法是:在程序的单线程启动阶段(single-threaded startup portion)手工调用所有reference-returning函数,这可消除与初始化有关的“竞速形式”(race conditions)。

  淡然啦,运用reference-returning函数防止“初始化次序问题”,前提是其中有一个对象而言合理的初始化次序。如果你有一个系统,其中对象A必须在对象B之前初始化,但A的初始化能否成功却又受制于B是否已初始化,这时候你就有麻烦了。坦白说你自作自受。只要避开如此病态的境况,此处描述的办法应该可以提供你良好的服务,至少在单线程程序中。

  既然这样,为了避免在对象初始化之前过早地使用它们,你需要做三件事。第一,手工初始化内置型non-member对象。第二,使用成员初始值列(member initialization lists)对付对象的所有成分。最后,在“初始化次序不确定性”氛围下加强你的设计。

  摘自《effective c++》

write by fgd

转载于:https://www.cnblogs.com/wendao/archive/2011/12/30/cpp_local_non_static_object_learning.html

effective c++ 跨编译单元之初始化次序 笔记相关推荐

  1. 跨编译单元之初始化次序

    所谓static对象,其寿命从被构造出来直到程序结束为止,因此 stack和heap-based对象都被排除.这种对象包括global对象,定 义于namespace作用域内的对象.在class内.在 ...

  2. C++ : 编译单元、声明和定义、头文件作用、防止头文件在同一个编译单元重复引用、static和不具名空间...

    转 自:http://www.cnblogs.com/rocketfan/archive/2009/10/02/1577361.html 1. 编译单元:一个.cc或.cpp文件作为一个编译单元,生成 ...

  3. Android编译系统环境过程初始化分析【转】

    本文转载自:http://blog.csdn.net/luoshengyang/article/details/18928789 Android源代码在编译之前,要先对编译环境进行初始化,其中最主要就 ...

  4. C++编译单元 内部链接 外部链接

    文章目录 编译单元 内部链接 外部链接简单解释 代码解释 外部链接 内部链接 C++ 中的内部链接 和外部链接 类型 编译单元 内部链接 外部链接简单解释 这是一个最简单最表面的解释,深入的解释应该要 ...

  5. $unit编译单元声明

    $unit编译单元声明 SystemVerilog含有编译单元. 相比Verilog,SystemVerilog增加了编译单元的概念.编译单元是同时编译的所有源文件.编译单元为软件工具提供了一种对整个 ...

  6. 资源不在java项目和构建路径上_编译单元不在Java项目的构建路径上-Maven

    今天,我已经在日食中导入了一个Maven项目.当我尝试自动建议时,当我添加一些代码时,它提示我"编译单元不在Java项目的构建路径上".我没有看到解决此问题的方法,但是都没有解决. ...

  7. 编译单元必须以 java_java中什么是编译单元

    Java中的编译单元 当编写一个Java源代码文件时,此文件以.java结尾,被称为编译单元. 1. 编译单元中可以有一个public类,且只能有一个public类,作为外界访问该类的接口,该类的名称 ...

  8. java lib 不在构建路径里面问题_svn - Eclipse“这个编译单元不在java项目的构建路径上”...

    svn - Eclipse"这个编译单元不在java项目的构建路径上" 我无法在Eclipse上使用自动完成功能. 我正在研究svn上的项目. 我通过进入Eclipse在Eclip ...

  9. linux mysql5.6编译_Linux 环境下编译安装MySQL5.6的笔记记录

    一.首先搭建好Linux环境,我这边使用的是redhat enterprise 6.5,并且建议磁盘划分逻辑卷,以便后期的扩容工作. 二.环境搭建好了之后,我们就要去准备MySQL的安装文件,到现在为 ...

  10. Effective Java(第三版) 学习笔记 - 第四章 类和接口 Rule20~Rule25

    Effective Java(第三版) 学习笔记 - 第四章 类和接口 Rule20~Rule25 目录 Rule20 接口优于抽象类 Rule21 为后代设计接口 Rule22 接口只用于定义类型 ...

最新文章

  1. java swing setborder_Swing编程边框(Border)的用法总结
  2. c/c++头文件中#ifndef/#define/#endif的用法
  3. 双11的前奏,电商正在用诺贝尔经济学奖的理论等你下锅
  4. Android PC投屏简单尝试—最终章2
  5. 华为荣耀畅玩8c主板电路图_【awinic inside】华为双新品发布!荣耀V9 play 携手荣耀畅玩6上演“青春加速度”!...
  6. Docker详解(二)——Docker技术原理与架构
  7. 学生管理系统IPO图_基于BIM技术的医院建筑运维管理系统构建
  8. 强化学习最强仿真平台--MuJoCo官方文档解读-Introduction
  9. 前端开发-HTML+CSS实现京东官网左侧导航条列表
  10. 获取素材列表返回40004 invalid media type.获取公众号素材mediaId
  11. “2014网站移动化大赛”已启动,个人网站全面进入“移动”时代?
  12. 斗罗大陆斗神再临服务器维修,斗罗大陆斗神再临攻略汇总:FAQ常见问题解答[多图]...
  13. Linux0号进程,1号进程,2号进程
  14. Python Flask基础教程(入门)
  15. Excel也可以播放MV
  16. 计算机二级office报名要求,计算机二级MS Office考试须知 这些你都需要知道
  17. 腾讯音乐娱乐数据分析4.15笔试
  18. Mozilla 考虑从 Firefox 剥离 XUL 和 XBL
  19. winrar远程代码执行漏洞(cve-2018-20250)
  20. 五、分享优秀的 RISC-V 项目资源

热门文章

  1. dontshrink解决ProGuard错误:java.lang.StackOverflowError
  2. 对外合作,你的作风就是公司的作风
  3. LINUX下载编译opusfile/opus-tools
  4. 代码中,对象类与管理类要分开
  5. 纽微特纪事:改个字串,竟然成了“二期工作”,还拖了几个月
  6. 纽微特记事:可笑的国际版
  7. 996是人类社会的倒退
  8. 如果没有证据,判断一个事件的主谋的方法
  9. 用网速作为手机信号强度
  10. 遇到ffmpeg错误:non monotonically increasing dts to muxer in stream