C ++中的静态初始化顺序

时间:2012-02-03 16:54:29

标签: c++

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

没有成语,你就有:

Foo bar;

用这个成语,你有:

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

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

谢谢, 戴夫

5 个答案:

答案 0 :(得分:2)

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

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

Foo.hh:

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,并且 在进入之前,允许实现在内存上涂鸦 构造函数的主体。但它总是在我的实践中起作用, 我已经将它用于许多不同的编译器。

答案 1 :(得分:1)

你可以将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;
};

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

答案 2 :(得分:0)

不能只是:

Foo& bar = bar();
某处继续使用吧?

另外,为什么不把这个成语实现为:

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

答案 3 :(得分:0)

James Kanze解决方案的简单变体。

但出于同样的原因失败了:

foo.h中

class Foo
{
};

Foo& getBar();

namespace
{
    Foo& bar = getBar();
}

Foo.cpp中

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

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

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

您需要一个间接级别才能使其失败:

Bar.h

class BarBar
{
    public: BarBar();
}

BarBar& barbar();

Bar.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;

FOOOOOO.h

class FOOOOOO
{
     public:
        FOOOOOO();
};
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`

答案 4 :(得分:0)

简单的解决方案是: 假设您可以重新编译所有代码 但如果你不能这样做,那么大多数解决方案都会失败。

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

Old Foo.h

extern Foo  bar;

Old Foo.cpp

#include "Foo.h"
Foo& bar;

然后您可以将其替换为:

New Foo.h

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()

New Foo.cpp

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