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

8

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

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


2
是的,那应该可以。例如,请参见指南中的“异常”部分。 - crayzeewulf
http://en.cppreference.com/w/cpp/language/storage_duration#Static_local_variables - zahir
你正在描述首次使用构造惯用语,是的,这是解决这个问题的有效方法。 - Fred Larson
我有一些全局变量,它们的构造函数依赖于其他翻译单元中的全局变量。为什么会这样?:( - Lightness Races in Orbit
@LightnessRacesinOrbit,我有一些用于反射等目的建模数据类型的类,还有一组常见的类型对象。如果可能的话,我不想让它们在每个翻译单元中都被复制。 - zneak
显示剩余2条评论
1个回答

7
你可以使用与标准流 std::cout 及其相关函数相同的方法,它被称为Schwarz Counter或Nifty Counter
如果你查看 GNU libstdc++ios_base.h 头文件:
// 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头文件:

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(...)
      { }
      }
  } 

上述代码将一个具有静态存储期的全局对象__ioinit嵌入到每个包含iostream头文件的翻译单元(.o)中。也就是说,每个.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.h时,my_global_logger都保证在第一次使用之前进行初始化,例如:my_global_logger->log("hello");

3
请复制外部网站中最相关的部分,而不仅仅是链接。SO答案应该能够自立,链接只应用于添加支持细节。 - Barmar
哪个标准库实现使用了这个混乱的代码?:( - Lightness Races in Orbit
@LightnessRacesinOrbit 这是标准的方式。换句话说,问题是哪个标准库实现不同? - Maxim Egorushkin
@Maxim 你是指“传统”的方式吗?标准没有规定实现。 - Lightness Races in Orbit
1
@LightnessRacesinOrbit 我不明白你为什么称它为黑客行为。 - Maxim Egorushkin
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接