全局状态有几种可能性(无论是否可变)。
如果您担心会遇到初始化问题,那么您应该使用该local static
方法来创建您的实例。
请注意,您提供的笨重单例设计不是强制性设计:
class Singleton
{
public:
static void DoSomething(int i)
{
Singleton& s = Instance();
// do something with i
}
private:
Singleton() {}
~Singleton() {}
static Singleton& Instance()
{
static Singleton S; // no dynamic allocation, it's unnecessary
return S;
}
};
// Invocation
Singleton::DoSomething(i);
另一种设计有些相似,但我更喜欢它,因为它使向非全局设计的过渡变得更加容易。
class Monoid
{
public:
Monoid()
{
static State S;
state = &s;
}
void doSomething(int i)
{
state->count += i;
}
private:
struct State
{
int count;
};
State* state;
};
// Use
Monoid m;
m.doSomething(1);
这里的净优势是状态的“全局性”是隐藏的,它是客户无需担心的实现细节。对缓存非常有用。
让我们,你会质疑设计:
- 你真的需要强制执行奇异性吗?
- 你真的需要在
main
开始之前构建对象吗?
奇异性通常被过分强调。C++0x 在这里会有所帮助,但即便如此,技术上强制执行奇点而不是依赖程序员来表现自己可能会非常烦人......例如在编写测试时:你真的想在每个单元测试之间卸载/重新加载你的程序吗?只是为了改变每一个之间的配置?啊。更简单地实例化一次并相信你的程序员同事......或功能测试;)
第二个问题是技术性的,而不是功能性的。如果您确实需要在程序的入口点之前进行配置,那么您可以在程序启动时简单地阅读它。
听起来可能很幼稚,但实际上在库加载期间计算存在一个问题:您如何处理错误?如果抛出,则不会加载库。如果你不扔并继续,你就处于无效状态。不是那么好笑,是吗?一旦真正的工作开始,事情就会简单得多,因为您可以使用常规的控制流逻辑。
如果您考虑测试状态是否有效......为什么不简单地在您要测试的地方构建所有内容?
最后,问题global
在于引入的隐藏依赖项。当依赖隐含地推断执行流程或重构的影响时,情况会好得多。
编辑:
关于初始化顺序问题:保证单个翻译单元中的对象按照定义的顺序进行初始化。
因此,根据标准,以下代码是有效的:
static int foo() { return std::numeric_limits<int>::max() / 2; }
static int bar(int c) { return c*2; }
static int const x = foo();
static int const y = bar(x);
仅在引用另一个翻译单元中定义的常量/变量时,初始化顺序才是一个问题。因此,static
只要对象仅引用static
同一翻译单元内的对象,自然就可以毫无问题地表达对象。
关于空间问题:as-if
规则可以在这里创造奇迹。非正式地,该as-if
规则意味着您指定一个行为并将其留给编译器/链接器/运行时来提供它,而无需关心它是如何提供的。这实际上是启用优化的原因。
因此,如果编译器链可以推断出一个常量的地址从未被取走,它可能会完全忽略该常量。如果它可以推断出几个常数总是相等的,并且再一次从不检查它们的地址,它可能会将它们合并在一起。