C++单例与全局静态对象

53 c++ singleton static global-variables

今天我的一个朋友问我为什么他更喜欢在全局静态对象上使用单例?我开始解释的方式是单例可以有状态与静态全局对象不会......但后来我不确定...因为这在C++中...(我来自C#)

一个优于另一个有什么优势?(在C++中)

vav*_*ava 57

实际上,在C++中,首选方式是本地静态对象.

Printer & thePrinter() {
    static Printer printer;
    return printer;
}
Run Code Online (Sandbox Code Playgroud)

这在技术上是单例,但这个函数甚至可以是类的静态方法.所以它保证在使用之前与全局静态对象不同,可以按任何顺序创建,这使得当一个全局对象使用另一个非常常见的场景时,可以无意义地失败.

通过调用创建新实例来做单例的常见方法是什么new使得对象析构函数将在程序结束时被调用.动态分配的单例不会发生这种情况.

另一个积极的方面是,在创建单例之前无法访问单例,即使是从其他静态方法或子类中也是如此.为您节省一些调试时间.

  • 这是制作单身人士的最佳方式.使用new在实际代码中并不常见,尽管互联网上有很多例子似乎表明它是好主意(不要相信它们). (8认同)
  • 如果您的单例引用另一个单例,则应禁止您编写另一行代码.当然还有写更多的单身人士.那么你就陷入了1970年代的思维模式,其中所有数据都必须是全局的. (6认同)
  • 实际上,没有"最佳"的单身人士制作方式.如果这个单例引用了析构函数中的另一个单例,它将无法正常工作. (3认同)
  • 请注意,这通常不是线程安全的(取决于实现:例如g ++将插入锁,但MSVC不会). (2认同)
  • ...然后你可以通过使构造函数私有化并使`thePrinter()`成为朋友函数来使`Printer'不会被其他任何人实例化. (2认同)
  • 我相信您知道这一点,但为了读者的利益,值得指出:全局静态对象仅容易在跨越多个翻译单元的组中“[可能]以任何顺序创建”(即 UB)。在单个 TU 内,它们保证按照声明的顺序创建。 (2认同)

rlb*_*ond 25

在C++中,不确定不同编译单元中静态对象的实例化顺序.因此,一个全局可能引用另一个未构造的全局,从而炸毁您的程序.单例模式通过将构造绑定到静态成员函数或自由函数来消除此问题.

有一个像样的总结在这里.

  • @rlbond:cin和cout没什么特别之处.它们与其他全局变量的处理方式相同.您可以使用其他技巧来确保它们已正确初始化. (3认同)
  • 有趣的是, std::cout 似乎工作正常,尽管它是一个普通的旧全局对象......我敢肯定,如果初始化顺序是一个大问题,标准委员会现在应该已经注意到了。如果您过度使用全局变量并使它们相互依赖,这只是一个问题。 (2认同)
  • @jalf:做功课。`cout` 和 `cin` 被标准明确指定在 `main()` 之前构造。其他全局对象不会得到这种处理。此外,线程安全的单例实现存在并且不难自己编写;你只需要一个双锁来防止双建。我个人尽量避免单例,我认为这种模式被过度使用了。但是你缺乏 C++ 知识并不是贬低我的理由。 (2认同)
  • 在这种情况下,您的双重锁定模式**不起作用**。静态对象保证在调用同一翻译单元中的任何函数之前被初始化,其中包括您想要初始化该对象的函数,这意味着它发生在您进入锁之前,因此它们并不多使用。:) (2认同)
  • 每个编译器供应商都可以自己定义 std 对象的初始化顺序。它们应该在任何用户代码运行之前被初始化。 (2认同)

jal*_*alf 8

今天我的一个朋友问我为什么他更喜欢在全局静态对象上使用单例?我开始解释的方式是单例可以有状态与静态全局对象不会......但后来我不确定...因为这在C++中...(我来自C#)

静态全局对象也可以在C#中具有状态:

class myclass {
 // can have state
 // ...
 public static myclass m = new myclass(); // globally accessible static instance, which can have state
}
Run Code Online (Sandbox Code Playgroud)

一个优于另一个有什么优势?(在C++中)

单例会破坏您的代码,而全局静态实例则不会.关于单身人士的问题已经有无数问题了.这是一个,另一个,或另一个.

简而言之,单身人士给你两件事:

  • 一个全局可访问的对象,和
  • 保证只能创建一个实例.

如果我们只想要第一点,我们应该创建一个全局可访问的对象.而为什么会我们曾经希望第二个?我们事先不知道将来如何使用我们的代码,为什么要将其删除并删除可能有用的功能?当我们预测"我只需要一个实例"时,我们通常会犯错误."我只需要一个实例"(正确答案就是创建一个实例)和"如果创建了多个实例,应用程序在任何情况下都无法正常运行"之间存在很大差异.它会崩溃,格式化用户的硬盘并在互联网上发布敏感数据"(这里的答案是:最有可能你的应用程序坏了,但如果不是,那么是的,单身就是你需要的)

  • 游戏没有理由应该使用单一的全局 RNG。为什么不减少争用并为每个线程分配一个争用呢?您甚至可能希望每个子系统都有一个不同的。或者在多人游戏场景中每个玩家。配置选项?我可以很容易地看出两个人的用途。您有一组处于活动状态的配置,以及用户当前正在修改但尚未应用的第二组配置。您可能**容易**拥有多个内存池。当然,单例模式有*有*合法的用途。但它们非常罕见,并且您列出的那些“不”在其中。 (2认同)