c++ - 如何确保以正确的顺序初始化全局变量?

标签 c++ c++14

我有全局变量,其构造函数依赖于不同翻译单元中的其他全局变量。我的理解是全局变量的初始化顺序是未指定的,所以这是不安全的。

如何确保我需要的全局变量最迟在首次访问时被初始化?例如,如果我在一个函数中创建一个静态变量并调用该函数来获取引用,它是否总是在函数第一次执行时被初始化?

最佳答案

您可以使用与标准流 std::cout 及其 friend 相同的方法。它叫做Schwarz Counter or Nifty Counter .

如果您查看 GNU libstdc++ios_base.h header :

// 27.4.2.1.6  Class ios_base::Init
// Used to initialize standard streams. In theory, g++ could use
// -finit-priority to order this stuff correctly without going
// through these machinations.
class Init
{
  friend class ios_base;
public:
  Init();
  ~Init();

private:
  static _Atomic_word   _S_refcount;
  static bool       _S_synced_with_stdio;
};

并进入 iostream header :

namespace std _GLIBCXX_VISIBILITY(default)
{
_GLIBCXX_BEGIN_NAMESPACE_VERSION

  extern istream cin;       /// Linked to standard input
  extern ostream cout;      /// Linked to standard output
  extern ostream cerr;      /// Linked to standard error (unbuffered)
  extern ostream clog;      /// Linked to standard error (buffered)

  // For construction of filebuffers for cout, cin, cerr, clog et. al.
  static ios_base::Init __ioinit;

_GLIBCXX_END_NAMESPACE_VERSION
} // namespace

然后进入ios_init.cc:

  ios_base::Init::Init()
  {
    if (__gnu_cxx::__exchange_and_add_dispatch(&_S_refcount, 1) == 0)
      {
    // Standard streams default to synced with "C" operations.
    _S_synced_with_stdio = true;

    new (&buf_cout_sync) stdio_sync_filebuf<char>(stdout);
    new (&buf_cin_sync) stdio_sync_filebuf<char>(stdin);
    new (&buf_cerr_sync) stdio_sync_filebuf<char>(stderr);

    // The standard streams are constructed once only and never
    // destroyed.
    new (&cout) ostream(&buf_cout_sync);
    new (&cin) istream(&buf_cin_sync);
    new (&cerr) ostream(&buf_cerr_sync);
    new (&clog) ostream(&buf_cerr_sync);
    cin.tie(&cout);
    cerr.setf(ios_base::unitbuf);
    // _GLIBCXX_RESOLVE_LIB_DEFECTS
    // 455. cerr::tie() and wcerr::tie() are overspecified.
    cerr.tie(&cout);

    // NB: Have to set refcount above one, so that standard
    // streams are not re-initialized with uses of ios_base::Init
    // besides <iostream> static object, ie just using <ios> with
    // ios_base::Init objects.
    __gnu_cxx::__atomic_add_dispatch(&_S_refcount, 1);
      }
  }

  ios_base::Init::~Init()
  {
    // Be race-detector-friendly.  For more info see bits/c++config.
    _GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&_S_refcount);
    if (__gnu_cxx::__exchange_and_add_dispatch(&_S_refcount, -1) == 2)
      {
        _GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_S_refcount);
    // Catch any exceptions thrown by basic_ostream::flush()
    __try
      { 
        // Flush standard output streams as required by 27.4.2.1.6
        cout.flush();
        cerr.flush();
        clog.flush();
      }
    __catch(...)
      { }
      }
  } 

上面的代码在每个包含iostream 头文件的翻译单元(.o) 中嵌入了一个全局对象__ioinit 和静态存储持续时间。 IE。每个 .o 都有自己的 __ioinit 拷贝。

所有具有静态存储持续时间的基本类型的对象在静态初始化阶段在启动时被零初始化(在 Linux 上这是 elf 对象的 .bss 部分),因此 _S_refcount 在动态初始化阶段之前被赋值为 0。

接下来,在动态初始化阶段调用这些__ioinit 对象的构造函数。每个构造函数递增 _S_refcount 并且观察到 _S_refcount 为 0 值的 __ioinit 对象驻留在首先被初始化的翻译单元中。该对象的构造函数初始化标准流。

在 C++ 标准库缺陷报告列表中有更多信息 Issue 369: io stream objects and static ctors .


您可以使用相同的方法来初始化您自己的全局对象。例如:

// DynamicInitializer.h
template<class T>
struct DynamicInitializer
{
    // These members have to be POD types to be zero-initialized at static initialization phase
    // prior to the dynamic initialization phase which invokes constructors of global objects.
    static T* instance_;
    static unsigned ref_count_;

    DynamicInitializer() {
        if(!ref_count_++)
            instance_ = new T;
    }

    ~DynamicInitializer() {
        if(!--ref_count_)
            delete instance_;
    }

    operator T&() const { return *instance_; }
    T* operator->() const { return instance_; }

    DynamicInitializer(DynamicInitializer const&) = delete;
    DynamicInitializer& operator=(DynamicInitializer const&) = delete;
};

template<class T>
unsigned DynamicInitializer<T>::ref_count_ = 0;

template<class T>
T* DynamicInitializer<T>::instance_ = 0;

用法:

// MyLogger.h

struct MyLogger
{
    void log(char const*);
};

// const makes static storage.
DynamicInitializer<MyLogger> const my_global_logger;

现在,无论何时包含 MyLogger.hmy_global_logger 都保证在首次使用之前进行初始化,例如my_global_logger->log("hello");

关于c++ - 如何确保以正确的顺序初始化全局变量?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32979033/

相关文章:

c++ - 如何调整 CListCtrl 列的宽度以适应每列中最长的字符串?

C++ 可变参数模板类型过滤转换

c++ - 'std::wstring_convert' 尽可能多地转换(从 UTF8 文件读取 block )

c++ - WinPCap 数据被截断

c++ - decltype(auto) foo() 在没有任何警告的情况下返回本地引用

c++ - 为什么 clang++ 不能推断出 lambda 映射的类型?

c++ - 反汇编 C++ 中全局变量的 .rodata 部分 (objdump -D)

具有嵌套表达式模板的 C++ 类

c++ - STL 错误 : use of deleted function when sorting a vector of objects in C++

c++ - 当此构造函数采用初始化列表并委托(delegate) vector 时,是什么导致此构造函数委托(delegate)给自身?