静态初始化顺序(以C++为单位)

Static Initialization Order in C++

本文关键字:C++ 为单位 初始化 顺序 静态      更新时间:2023-10-16

C++中静态初始化顺序问题的常见解释是"首次使用时构造"习惯用法。这个习惯用法在你的静态对象周围放了一个函数包装器。

如果没有这个成语,你会有:

Foo bar;

有了这个成语,你会有:

Foo &bar()
{
   static Foo *ptr = new Foo();
   return *ptr;
}

从第一个移动到第二个需要bar的所有用法都从bar更改为bar()。我处于无法进行此更改的情况(太多使用网站,失去使用operator<<的自然性(。我尝试了各种语法扭曲,以找到一种方法来实现这个不需要更改调用站点的习语。 我找不到。社区中是否有人有办法允许这样做?

谢谢戴夫

它远非完美,但你总是可以做实现iostream确保std::cinstd::cout的初始化。(这有时被称为漂亮的反习语,或施瓦茨计数器,在技术的入侵者之后。有几个变体,但基本思想取决于以下事实:顺序初始化保证在单个翻译单元内,因此,如果您在标头中定义某个特殊类型的(静态(实例,它将be(通常,因为标头包含在顶部(之前构造源文件中的任何内容。 此静态的构造函数实例检查全局标志或计数器;如果值为 0,则为初始化全局对象,并递增计数器,以便以下构造函数不会初始化它。 (没有顺序计数器的初始化问题,因为它依赖于零初始化。 唯一的问题是如何声明对象本身。我认为在最早的版本中,它是在汇编程序中声明的,作为一个足够字节的数组。 我发现什么有效(虽然不能保证按标准(是声明一个特殊的、无操作的构造函数,并调用在变量的"初始化"中,当然,初始化对象使用放置 NEW。

通过一个简单的例子,这可能会更清楚:

呜:

class Foo
{
    enum Hidden { noop };
    Foo( Hidden ) {}
public:
    Foo();    //  The real constructor.
    static Foo bar;
    class FoobarInitializer
    {
    public:
        FoobarInitializer();
    }
};
static FoobarInitializer initializeFoobar;

Foo.cc:

namespace {
int initCount;
}
Foo Foo::bar( Foo::noop );
Foo::FoobarInitializer::FoobarInitializer()
{
    if ( initCount == 0 ) {
        new (&Foo::bar) Foo();
    }
    ++ initCount;
}

如果bar不是Foo的成员,则此技术同样有效,但是您需要公开更多内容。 (初始值设定项可以是朋友,但至少,Foo::noop必须是公开的。

我再说一遍,这不能保证:Foo::Foo( noop )可能是在初始化类构造它之后调用bar,并且允许实现在输入之前在内存上涂鸦构造函数的主体。 但它在实践中对我来说总是有效,我已经在许多不同的编译器中使用了它。

您可以将Foo重命名为FooImpl之类的名称,保留"首次使用时构造"的习惯用法。然后:

struct Foo
{
  Foo()
  : _impl(FooImpl())
  {}
  // wrappers for the FooImpl methods
  bool my_foo_impl_func()
  {
    return _impl.my_foo_impl_func();
  }

private:
  FooImpl& _impl;
};

使用此包装器,无需更改其余代码。

你不能只是:

Foo& bar = bar();

在某个地方继续使用酒吧?

另外,为什么不将成语实现为:

Foo& bar()
{
    static Foo foo;
    return foo;
}

James Kanze 解決方案的簡單變體。

但由于同样的原因而失败:

福.h

class Foo
{
};
Foo& getBar();
namespace
{
    Foo& bar = getBar();
}

傅.cpp

#include "Foo.h"
Foo& getBar() {static Foo bar; return bar;}

现在,在包含 Foo.h 的每个文件中,我们引入了一个匿名命名空间,用于初始化本地对象栏(即引用(。所以它在你使用bar之前被初始化(因为你必须包括Foo.h才能知道使用它们的Foo对象是什么(。

这个(和詹姆斯·坎泽(解决方案可能失败的原因是:

您需要一定程度的间接才能使其失败:

酒吧

class BarBar
{
    public: BarBar();
}
BarBar& barbar();

酒吧.cpp

#include "Bar.h"
#include "Foo.h"
BarBar& barbar() { static BarBar b; return b;}
BarBar::BarBar()
{
    // Here Bar is a Foo
    bar.somFooThing();
}
// This works because you have to include Foo.h
// Which makes sure that bar is initialized correctly because the
// order of initialization inside the compilation unit is defined.
BarBar  barGlobal;

呜��

class FOOOOOO
{
     public:
        FOOOOOO();
};
呜.cpp
// Notice we don't need to include Foo.h we are not using it directly.
#include "Bar.h"
FOOOOOO::FOOOOOO()
{
      barbar().doBArStuff();     
}
// Here is where the problem occures.
FOOOOOO  FCUK_UP;
// If this object is constructed first.
// It will call barbar() in its constructor.
// Which will initialize an object of Foo in its constructor.
// But at this point we have not forced any initialization of `Foo bar`

简单的解决方案是:假设您可以重新编译所有代码。
但是,如果您不能做到这一点,这里的大多数解决方案都将失败。

假设你的旧Foo看起来像这样:

老福

extern Foo  bar;

老福.cpp

#include "Foo.h"
Foo& bar;

然后,您可以将其替换为:

新福

Foo& getBar();
// Not like I like this but it would be better to force users to change
// how they use bar() rather than use this hack.
#define   bar    getBar()

新福.cpp

#include "Foo.h"
Foo& getBar() { static Foo bar; return bar; }