静态初始化顺序(以C++为单位)
Static Initialization Order in C++
C++中静态初始化顺序问题的常见解释是"首次使用时构造"习惯用法。这个习惯用法在你的静态对象周围放了一个函数包装器。
如果没有这个成语,你会有:
Foo bar;
有了这个成语,你会有:
Foo &bar()
{
static Foo *ptr = new Foo();
return *ptr;
}
从第一个移动到第二个需要bar
的所有用法都从bar
更改为bar()
。我处于无法进行此更改的情况(太多使用网站,失去使用operator<<
的自然性(。我尝试了各种语法扭曲,以找到一种方法来实现这个不需要更改调用站点的习语。 我找不到。社区中是否有人有办法允许这样做?
谢谢戴夫
它远非完美,但你总是可以做实现iostream确保std::cin
和std::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; }
- 以天C++为单位的两个时间戳之间的差异
- 如何以毫秒为单位获取开始时间和 now() 之间的毫秒差异(以 C++为单位?
- 从原始字节解码协议缓冲区(以 C++为单位)
- 如何在 c++ 中确定一条指令(以字节为单位)在哪里结束,另一条指令从哪里开始?
- 编写以 C++ 为单位返回值的函数
- 以C++为单位进行运行长度编码
- 如何找到两个日期之间的时间差异(以秒和纳秒为单位)?
- arr[n] 是否以 C++ 为单位打印数组的长度?
- 字符串数组上的 sizeof 运算符以 C++ 为单位给出不同的输出
- 以 C++ 为单位具有输出限制的排列
- 以 GDB 为单位指定浮点精度
- 整数数据如何以位为单位存储在内存中?不是右对齐吗?
- 如何在没有硬编码的情况下以C++为单位获取类数组的长度?
- 指针引用的生存期(以 C++为单位)
- 以字符为单位设置控制台大小
- 获取嵌套 stl 容器的大小(以字节为单位)
- 有没有一种 STL 方法可以找到字符串的所有排列,给出一个以 C++ 为单位的大小?
- 二进制数组中最大连续 1 的起始和结束索引,以 C++ 为单位
- 正向声明的枚举,默认值以.h为单位
- 常量"C"占用的空间(以字节为单位)