C++:使用int vs struct对静态成员变量进行依赖初始化的静态

标签 c++ c++11 static

给定一个从另一个类的静态成员变量初始化的静态成员变量,非文字struct ii有时默认初始化为0 333。这取决于编译或链接顺序。 伪代码演示:

class StaticClass: // file 'ONE.cpp/.hpp'
    static int i = 2
    static struct ii { int a = 333 }

class ABC: // file 'abc.cpp'
    static int abc_i = StaticClass::i   // always 2
    static struct abc_ii = StaticClass::ii // sometimes 0, sometimes 333

调用 g++ -std=c++11 abc.cpp ONE.cpp && ./a.out 结果 i = 2/ii = 0 (gcc 4.8 .1,与 clang++ 3.7 相同;-Wall -Wextra 从不提示)。

但是调用 g++ -std=c++11 ONE.cpp abc.cpp && ./a.out 导致 i = 2/ii = 333 !

ONE.o abc.oabc.o ONE.o 以及以一种或另一种方式连接文件时也会发生同样的情况:

cat ONE.cpp abc.cpp > X.cpp && g++ X.cpp && ./a.out 对比 cat abc.cpp ONE.cpp > Y.cpp && g++ Y.cpp && ./a.out

删除包含并在单个文件中四处移动代码,当存在此顺序时默认初始化为 0:

const OneI ABC::def_ii = StaticClass::ii; const OneI StaticClass::ii = OneI{333};

还有一个到 333 的顺序:

const OneI StaticClass::ii = OneI{333}; const OneI ABC::def_ii = StaticClass::ii;

为什么这甚至会发生在两个独立的编译单元中?可以通过始终强制执行后者排序来以某种方式避免这种情况吗?在 ABC 中使用指向 StaticClass::ii 的静态指针是否安全(不过我不想这样做)?

完整的 C++ 代码:


/* File: abc.cpp */

#include <iostream>
#include "ONE.hpp"

struct ABC {
  ABC();

  static const int def_i;
  static const OneI def_ii;
  void arg_i(const int &x) { std::cout << "i = " << x << " ";};
  void arg_ii(const OneI &x) { std::cout << "/ ii = " << x.a << " ";};

};

ABC::ABC() {
  arg_i(def_i);
  arg_ii(def_ii);
}

const int ABC::def_i = StaticClass::i;
const OneI ABC::def_ii = StaticClass::ii;

int main() {
  ABC a;
  std::cout << '\n';
}
/* End: abc.cpp */

/* File: ONE.cpp */

#include <iostream>

#include "ONE.hpp"

const int StaticClass::i = 2;
const OneI StaticClass::ii = OneI{333};

/* End: ONE.cpp */

/* File: ONE.hpp */

#include <iostream>

#ifndef One
#define One

struct OneI {
  OneI(int a_) : a(a_) { }
  int a;
};

struct StaticClass {
  const static int i;
  const static OneI ii;
};

#endif // One header guard

/* End: ONE.hpp */

最佳答案

恭喜!您遇到了 static initialization order fiasco .

静态对象的初始化顺序没有跨多个翻译单元定义。

StaticClass::iiONE.cpp 中定义,ABC::def_iiabc.cpp。因此 StaticClass::ii 可能会也可能不会在 ABC::def_ii 之前初始化。由于 ABC::def_ii 的初始化使用了 StaticClass::ii 的值,因此该值将取决于 StaticClass::ii 是否已初始化.

定义了翻译单元的静态对象的初始化顺序。对象按照它们被定义的顺序被初始化。因此,当您连接源文件时,定义了初始化顺序。但是,当您以错误的顺序连接文件时,定义的初始化顺序是错误的:

const OneI ABC::def_ii = StaticClass::ii; // StaticClass::ii wasn't initialized yet
const OneI StaticClass::ii = OneI{333};

Can this be avoided somehow by enforcing the latter ordering all the time?

最简单的解决方案是在同一个翻译单元中以正确的顺序定义两个对象。一个更通用的解决方案是使用 Construct On First Use Idiom 初始化静态对象。 .

Is using a static pointer in ABC to StaticClass::ii safe (I'd prefer not to, though)?

只要在定义指向对象的另一个翻译单元中的静态对象初始化期间不使用指针的解引用值,就可以将 ABC::def_ii 替换为一个指针是安全的。


StaticClass::ii 将在静态初始化阶段进行零初始化††。静态初始化顺序失败涉及动态初始化阶段††

†† C++ 标准草案 [basic.start.static]

If constant initialization is not performed, a variable with static storage duration ([basic.stc.static]) or thread storage duration ([basic.stc.thread]) is zero-initialized ([dcl.init]). Together, zero-initialization and constant initialization are called static initialization; all other initialization is dynamic initialization. Static initialization shall be performed before any dynamic initialization takes place. [ Note: The dynamic initialization of non-local variables is described in [basic.start.dynamic]; that of local static variables is described in [stmt.dcl]. — end note ]

关于C++:使用int vs struct对静态成员变量进行依赖初始化的静态,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40107792/

相关文章:

c++ - 将二进制数据写入 ostream 时减少代码

c++ - 将 make_shared 与 shared_ptr<T> 一起使用仅对 T < 56 字节有益?

c# - 主窗口中的静态成员与实例成员

c# - 扩展还是静态功能?

C++:隐藏基本静态成员

c++ - 尝试将 C 代码转换为 C++ - 交互式二进制欧几里得算法

c++ - 模板依赖实现

c++ - 引用引用多个对象,怎么可能?

c++ - C++ 中的 rand() 函数给出了超出范围的数字

c++ - 如何将 boost::hana::Sequence 的元素全部满足一个概念来表述为一个概念?