在C ++程序中我需要一些辅助常量对象,这些对象将被实例化一次,最好是在程序启动时。这些对象通常在同一个翻译单元中使用,因此最简单的方法是将它们设置为静态:
static const Helper h(params);
但是有static initialization order问题,所以如果Helper
引用其他静态信息(通过params
),这可能会导致UB。
另一点是我最终可能需要在几个单元之间共享此对象。如果我将它留下static
并放入.h文件,那将导致多个对象。我可以通过使用extern
等来避免这种情况,但这最终会引发相同的初始化顺序问题(而不是说它看起来很像C-ish)。
我想过单身人士,但由于样板代码和不方便的语法(例如MySingleton::GetInstance().MyVar
),这将是一种过度杀伤 - 这些对象是助手,所以他们应该简化事情,而不是让它们复杂化......
相同的C ++ FAQ mentions此选项:
Fred& x()
{
static Fred* ans = new Fred();
return *ans;
}
这真的被使用并被视为好事吗?我应该这样做,还是你会建议其他选择?感谢。
编辑:我应该澄清为什么我真的需要帮助器:它们非常像常规常量,并且可以预先计算,但在运行时更方便。我宁愿在main之前实例化它们,因为它会自动解决多线程问题(在C ++ 03中没有保护本地静态)。另外,正如我所说,它们通常仅限于翻译单元,因此导出它们并在main()中初始化是没有意义的。您可以将它们视为常量,但仅在运行时知道。答案 0 :(得分:5)
全球状态有多种可能性(无论是否可变)。
如果您担心初始化问题,则应使用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
规则意味着您指定一个行为并将其留给编译器/链接器/运行时来提供它,而无需在世界范围内如何提供它。这实际上是可以实现优化的。
因此,如果编译器链可以推断永远不会采用常量的地址,那么它可能会完全忽略常量。如果它可以推断出几个常量总是相等的,并且再次确认它们的地址从未被检查过,那么它可以将它们合并在一起。
答案 1 :(得分:2)
是的,您可以使用 Construct On First Use 成语,如果它可以简化您的问题。它比其他全局对象初始化依赖的全局对象总是更好。
另一种选择是Singleton Pattern。两者都可以解决类似问题。但是你要决定哪种情况更适合你的情况并满足你的要求。
据我所知,没有比这两个appproach更“好”的了。
答案 2 :(得分:0)
单身人士和全球物品通常被认为是邪恶的。最简单和最灵活的方法是在main
函数中实例化对象并将此对象传递给其他函数:
void doSomething(const Helper& h);
int main() {
const Parameters params(...);
const Helper h(params);
doSomething(h);
}
另一种方法是使辅助函数成为非成员。也许他们根本不需要任何状态,如果他们这样做,你可以在调用它们时传递一个有状态的对象。
我认为没有什么能反对FAQ中提到的本地静态习语。它很简单,应该是线程安全的,如果对象不可变,它也应该很容易模拟,并且不会在远处引入任何动作。
答案 3 :(得分:0)
在Helper
运行之前,main
是否需要存在?如果没有,请将(一组?)全局指针变量初始化为0
。然后使用main以确定的顺序使用常量状态填充它们。如果你愿意,你甚至可以制作辅助功能来为你解除引用。