使用什么来代替静态变量

发布于 2024-10-14 22:20:03 字数 1037 浏览 3 评论 0原文

在 C++ 程序中,我需要一些辅助常量对象,这些对象将被实例化一次,最好是在程序启动时实例化。这些对象主要在同一个翻译单元中使用,因此最简单的方法是将它们设为静态:

static const Helper h(params);

但是还有这个 静态初始化顺序 问题,因此如果 Helper 引用其他一些静态变量(通过 params ),这可能会导致 UB。

另一点是我最终可能需要在多个单元之间共享这个对象。如果我只是将其保留为静态并放入 .h 文件中,则会产生多个对象。我可以通过使用 extern 等来避免这种情况,但这最终会引发相同的初始化顺序问题(并不是说它看起来非常 C 风格)。

我考虑过单例,但由于样板代码和不方便的语法(例如 MySingleton::GetInstance().MyVar),这太过分了 - 这些对象是助手,所以它们应该简化事情,不要让它们复杂化...

相同的 C++ FAQ 提及 这个选项:

 Fred& x()
 {
   static Fred* ans = new Fred();
   return *ans;
 } 

这真的被使用并被认为是一件好事吗?我应该这样做,还是你会建议其他选择?谢谢。

编辑:我应该澄清为什么我实际上需要这些助手:它们非常像普通常量,并且可以预先计算,但在运行时执行此操作更方便。我更喜欢在 main 之前实例化它们,因为它会自动解决多线程问题(在 C++03 中本地静态不受保护)。另外,正如我所说,它们通常仅限于翻译单元,因此导出它们并在 main() 中初始化是没有意义的。您可以将它们视为常量,但仅在运行时才知道。

in a C++ program I need some helper constant objects that would be instantiated once, preferably when the program starts. Those objects would mostly be used within the same translation unit, so the simplest way to do this would be to make them static:

static const Helper h(params);

But then there is this static initialization order problem, so if Helper refers to some other statics (via params), this might lead to UB.

Another point is that I might eventually need to share this object between several units. If I just leave it static and put in a .h file, that would lead to multiple objects. I could avoid that by bothering with extern etc, but this can finally provoke the same initialization order issues (and not to say it looks very C-ish).

I thought about singletons, but that would be overkill due to the boilerplate code and inconvenient syntax (e.g. MySingleton::GetInstance().MyVar) - those objects are helpers, so they are supposed to simplify things, not to complicate them...

The same C++ FAQ mentions this option:

 Fred& x()
 {
   static Fred* ans = new Fred();
   return *ans;
 } 

Is this really used and considered a good thing? Should I do it this way, or would you suggest other alternatives? Thanks.

EDIT: I should have clarified why I actually need that helpers: they are very like normal constants, and could have been pre-calculated, but it is more convenient to do that at runtime. I would prefer to instantiate them before main, as it automatically resolves multi-threading issues (which local statics are not protected against in C++03). Also, as I said, they would often be limited to a translation unit, so it does not make sense to export them and initialize in main(). You can think of them as just constants but only known at runtime.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

不知在何时 2024-10-21 22:20:03

全局状态有多种可能性(无论是否可变)。

如果您担心会遇到初始化问题,那么您应该使用本地静态方法来创建实例。

请注意,您提供的笨重的单例设计不是强制性设计:

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

初始化顺序仅在引用另一个翻译单元中定义的常量/变量时才是问题。因此,静态对象自然可以毫无问题地表达,只要它们仅引用同一翻译单元内的静态对象即可。

关于空间问题:as-if 规则在这里可以创造奇迹。通俗地说,as-if 规则意味着您指定一种行为并将其留给编译器/链接器/运行时来提供它,而无需关心它是如何提供的。这才是真正实现优化的原因。

因此,如果编译器链可以推断出常量的地址从未被占用,则它可能会完全忽略该常量。如果它可以推断出几个常量将始终相等,并且它们的地址再次从未被检查过,则它可能会将它们合并在一起。

There are several possibilities for global state (whether mutable or not).

If you fear that you'll have an initialization issue, then you should use the local static approach to create your instance.

Note that the clunky singleton design you present is not mandatory design:

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

Another design is somewhat similar, though I much prefer it because it makes transition to a non-global design much easier.

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

The net advantage here is that the "global-ness" of the state is hidden, it's an implementation details that clients need not worry about. Very useful for caches.

Let us, will you, question the design:

  • do you actually need to enforce the singularity ?
  • do you actually need the object be built before main starts ?

Singularity is generally over-emphasized. C++0x will help here, but even then, technically enforcing singularity rather than relying on programmers to behave themselves can be very annoying... for example when writing tests: do you really want to unload/reload your program between each unit test just to change the configuration between each one ? Ugh. Much more simple to instantiate it once and have faith in your fellow programmers... or the functional tests ;)

The second question is more technical, than functional. If you do need the configuration before the entry point of your program, then you can simply read it when it starts.

It may sound naive, but there is actually one issue with computing during the library load: how do you handle errors ? If you throw, the library is not loaded. If you do not throw and go on, you are in an invalid state. Not so funny, is it ? Things are much simpler once the real work has begun, because you can use the regular control-flow logic.

And if you think about testing whether the state is valid or not... why not simply building everything at the point where you'd test ?

Finally, the very issue with global is the hidden dependencies that are introduced. It's much better when dependencies are implicit to reason about the flow of execution, or the impacts of a refactoring.


EDIT:

Regarding initialization order issues: objects within a single translation unit are guaranteed to be initialized in the order they are defined.

Therefore, the following code is valid according to the standard:

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

The initialization order is only an issue when referencing constants / variables defined in another translation unit. As such, static objects can naturally be expressed without issues as long as they only refer to static objects within the same translation unit.

Regarding the space issue: the as-if rule can do wonders here. Informally the as-if rule means that you specify a behavior and leave it up to the compiler/linker/runtime to provide it, without a care in the world for how it is provided. This is what actually enables optimizations.

Therefore, if the compiler chain can infer that the address of a constant is never taken, it may elide the constant altogether. If it can infer that several constants will always be equal, and once again that their address are never inspected, it may merge them together.

随心而道 2024-10-21 22:20:03

是的,您可以使用首次使用时构建 习语,如果它简化了你的问题。它总是比更好,因为全局对象的初始化依赖其他全局对象。

另一种选择是单例模式。两者都可以解决类似的问题。但您必须决定哪个更适合具体情况并满足您的要求。

据我所知,没有什么比这两种方法“更好”了。

Yes, you can use Construct On First Use Idiom if it simplifies your problem. It's always better than global objects whose initialization depend on other global objects.

The other alternative is Singleton Pattern. Both can solve similar problem. But you've to decide which suits the situation better and fulfill your requirement.

To the best of my knowledge, there is nothing "better" than these two appproaches.

牵强ㄟ 2024-10-21 22:20:03

单例和全局对象通常被认为是邪恶的。最简单、最灵活的方法是在 main 函数中实例化该对象,并将该对象传递给其他函数:

void doSomething(const Helper& h);
int main() {
  const Parameters params(...);
  const Helper h(params);
  doSomething(h);
}

另一种方法是使辅助函数成为非成员。也许它们根本不需要任何状态,如果需要,您可以在调用它们时传递一个有状态对象。

我认为没有什么可以反对常见问题解答中提到的本地静态习惯用法。它很简单并且应该是线程安全的,如果对象不可变,它也应该很容易模拟并且不会引入远距离操作。

Singletons and global objects are often considered evil. The simplest and most flexible way is to instantiate the object in your main function and pass this object to other functions:

void doSomething(const Helper& h);
int main() {
  const Parameters params(...);
  const Helper h(params);
  doSomething(h);
}

Another way is to make the helper functions non-members. Maybe they don't need any state at all, and if they do, you can pass a stateful object when you call them.

I think nothing speaks against the local static idiom mentioned in the FAQ. It is simple and should be thread-safe, and if the object isn't mutable, it should also be easily mockable and introduce no action at a distance.

氛圍 2024-10-21 22:20:03

Helper 是否需要在 main 运行之前存在?如果没有,请将(一组?)全局指针变量初始化为0。然后使用 main 以确定的顺序用常量状态填充它们。如果您愿意,您甚至可以创建辅助函数来为您取消引用。

Does Helper need to exist before main runs? If not, make a (set of?) global pointer variables initialized to 0. Then use main to populate them with the constant state in a definitive order. If you like you can even make helper functions that do the dereference for you.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文