为什么要避免 C++ 中的单例

Eug*_*rks 4 c++ singleton design-patterns

人们到处都使用单例。最近从 stackoverflow 读到一些线程,在 C++ 中应该避免单例,但不清楚为什么会这样。

有些人可能会担心未删除的指针会导致内存泄漏,诸如异常之类的事情会跳过内存回收代码。但是 auto_ptr 会解决这个问题吗?

lef*_*cus 5

一般来说,如另一个答案中所述,您应该避免可变的全局数据。它引入了跟踪代码副作用的困难。

但是,您的问题专门针对 C++。例如,您可以拥有值得在单例中共享的全局不可变数据。特别是在 C++ 中,在多线程环境中安全地初始化单例几乎是不可能的。

多线程环境

您可以使用“首次使用时构造”的习惯用法来确保单例在需要时正确初始化:http : //www.parashift.com/c++-faq-lite/static-init-order.html .

但是,如果您有 2 个(或更多)线程,它们都在完全相同的时间第一次尝试访问单例,会发生什么?如果共享的不可变数据是您的calculateSomeData线程所需的数据,并且您同时初始化其中的几个线程,则这种情况并不像看起来那么遥不可及。

阅读上面 C++ FAQ Lite 中链接的讨论,您可以首先看到这是一个复杂的问题。添加线程使它变得更加困难。

在 Linux 上,使用 gcc,编译器为您解决了这个问题 - 静态在互斥锁中初始化,并且代码对您来说是安全的。这是一个增强,标准不需要这样的行为。

在 MSVC 中,编译器不为您提供此实用程序,反而会导致崩溃。你可能会想“没关系,我会在我的第一次使用初始化周围放置一个互斥锁!” 然而,互斥体本身也面临着完全相同的问题,它本身需要是静态的。

确保你的单例对于线程使用是安全的唯一方法是在任何线程启动之前在程序的早期初始化它。这可以通过调用 main之前初始化单例的技巧来完成。

依赖其他单例的单例

这个问题主要可以通过构造 on first use idiom 来解决,但是如果您在初始化任何线程之前遇到初始化它们的问题,您可能会引入新的问题。

跨平台兼容性

如果您计划在多个平台上使用您的代码并编译共享库,则可能会出现一些问题。由于没有指定 C++ ABI 接口,每个编译器和平台以不同的方式处理全局静态。例如,除非在 MSVC 中显式导出符号,否则每个 DLL 都会有自己的单例实例。在 Linux 上,单例将在共享库之间隐式共享。