单例:应该如何使用
Posted
技术标签:
【中文标题】单例:应该如何使用【英文标题】:Singleton: How should it be used 【发布时间】:2010-09-10 08:06:16 【问题描述】:编辑: 在另一个问题中,我提供了一个答案,其中包含许多关于单身人士的问题/答案的链接:More info about singletons here:
所以我已经阅读了线程Singletons: good design or a crutch? 争论仍在继续。
我将单例视为一种设计模式(好的和坏的)。 Singleton 的问题不在于模式,而在于用户(对不起大家)。每个人和他们的父亲都认为他们可以正确地实施一项(根据我所做的许多采访,大多数人都不能)。同样因为每个人都认为他们可以实现正确的单例,所以他们滥用模式并在不合适的情况下使用它(用单例替换全局变量!)。
所以需要回答的主要问题是:
什么时候应该使用单例 如何正确实现单例我对这篇文章的希望是,我们可以在一个地方(而不是必须用谷歌搜索多个站点)收集有关何时(以及如何)正确使用 Singleton 的权威来源。同样合适的是反使用和常见不良实现的列表,解释它们为什么无法工作以及好的实现它们的弱点。
所以开始吧: 我会举手说这是我用的,但可能有问题。 我喜欢“Scott Myers”在他的书“Effective C++”中对主题的处理
使用单例的好情况(不多):
日志框架 线程回收池
/*
* C++ Singleton
* Limitation: Single Threaded Design
* See: http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf
* For problems associated with locking in multi threaded applications
*
* Limitation:
* If you use this Singleton (A) within a destructor of another Singleton (B)
* This Singleton (A) must be fully constructed before the constructor of (B)
* is called.
*/
class MySingleton
private:
// Private Constructor
MySingleton();
// Stop the compiler generating methods of copy the object
MySingleton(MySingleton const& copy); // Not Implemented
MySingleton& operator=(MySingleton const& copy); // Not Implemented
public:
static MySingleton& getInstance()
// The only instance
// Guaranteed to be lazy initialized
// Guaranteed that it will be destroyed correctly
static MySingleton instance;
return instance;
;
好的。让我们一起得到一些批评和其他实现。 :-)
【问题讨论】:
如果您后来决定想要多个记录器怎么办?还是多个线程池?如果您只想要一个记录器,则只需创建一个实例并将其设为全局。单例只有在你绝对需要只有一个并且它需要是全球性的时候才是好的,恕我直言。 谁说框架只能有 1 个记录器实例。一个代表框架的单例。然后,框架可以为您提供特定的记录器。 是的。我不会使用 singeltong 作为线程池。只是抛出想法来激发答案。 @Dan Singleton 实现策略模式。行为是从单例中抽象出来的。 Singleton 是一个单一的入口点。不要有两个记录器,有一个可以决定如何记录的记录器。你不能一次只输出一个日志,不需要两个。 Xaade:如果你想登录到两个文件怎么办?还是到数据库?还是网络套接字?还是 GUI 小部件?重点是,不要添加人为的限制——没有必要。您有多少次不小心创建了两个 for 循环而不是一个?如果您只想要一个记录器,则只创建一个。 【参考方案1】:答案:
在以下情况下使用单例:
您需要在系统中拥有一个且只有一个类型的对象在以下情况下不要使用单例:
你想节省内存 您想尝试新事物 你想炫耀你知道多少 因为其他人都在这样做(参见***中的 cargo cult programmer) 在用户界面小部件中 应该是缓存 在字符串中 在会话中 我可以去一整天如何创建最好的单例:
越小越好。我是极简主义者 确保它是线程安全的 确保它永远不会为空 确保只创建一次 延迟或系统初始化?满足您的要求 有时操作系统或 JVM 会为您创建单例(例如,在 Java 中,每个类定义都是一个单例) 提供析构函数或以某种方式找出如何处置资源 使用很少的内存【讨论】:
其实我觉得你也不太对。我会改写为:“如果您需要在系统中拥有一个且只有一个类型的对象,并且您需要可以全局访问它”强调需要是我的 - 如果方便就不要这样做,除非你必须拥有它。 你也错了。如果您需要一个且只有一个对象,则创建一个且仅一个。如果没有合乎逻辑的方式可以容纳两个实例而不会对应用程序造成不可逆转的破坏,那么您应该考虑将其设置为单例。然后是另一方面,全局访问:如果您不需要对实例的全局访问,它不应该是单例。 关闭修改,打开扩展。问题是您不能将单件扩展为双件或三件。它被困在单例中。 @enzom83:大写字母 S 的单例包含确保其单一性的代码。如果您只想要一个实例,您可能会丢失该代码并简单地自己创建一个实例...为您节省单个实例的内存,加上避免单一执行代码所节省的内存 - - 这也意味着如果您的需求发生变化,也不会牺牲创建第二个实例的能力。 “如果您需要在系统中拥有一个且只有一个类型的对象” - “......并且永远不想在单元测试中模拟该对象。”【参考方案2】:单身人士让你能够在一个职业中结合两种不良特征。这几乎在所有方面都是错误的。
单身人士给你:
-
对对象的全局访问,以及
保证这种类型的对象不能超过一个可以被创建
第一是直截了当的。全局变量通常很糟糕。除非我们真的需要,否则我们永远不应该让对象全局可访问。
第二个可能听起来有道理,但让我们考虑一下。您最后一次**意外*创建一个新对象而不是引用现有对象是什么时候?由于这是标记为 C++,让我们使用该语言的示例。你是不是经常不小心写了
std::ostream os;
os << "hello world\n";
当你打算写作时
std::cout << "hello world\n";
当然不是。我们不需要针对这种错误进行保护,因为这种错误不会发生。如果是这样,正确的反应是回家睡 12-20 小时,希望你感觉好些。
如果只需要一个对象,只需创建一个实例。如果一个对象应该是全局可访问的,则将其设为全局对象。但这并不意味着不可能创建它的其他实例。
“只有一个实例是可能的”约束并不能真正保护我们免受可能的错误的影响。但它确实使我们的代码很难重构和维护。因为我们经常稍后发现我们确实需要多个实例。我们确实拥有多个数据库,我们确实拥有多个配置对象,我们确实需要多个记录器。我们的单元测试可能希望能够在每个测试中创建和重新创建这些对象,举一个常见的例子。
所以当且仅当我们需要 both 它提供的特征时才应该使用单例:如果我们需要全局访问(这很少见,因为全局变量通常是气馁)并且我们需要防止任何人曾经创建多个类的实例(在我看来这听起来像是一个设计问题)。我能看到的唯一原因是如果创建两个实例会破坏我们的应用程序状态 - 可能是因为该类包含许多静态成员或类似的愚蠢。在这种情况下,显而易见的答案是修复该类。它不应该依赖于成为唯一的实例。
如果您需要对某个对象进行全局访问,请将其设为全局访问,例如 std::cout
。但不要限制可以创建的实例数量。
如果您绝对肯定需要将一个类的实例数限制为一个,并且无法安全地处理创建第二个实例,那么请强制执行。但也不要使其全局可访问。
如果您确实需要这两个特征,那么 1) 使其成为单例,2) 让我知道您需要它的用途,因为我很难想象这样的情况。
【讨论】:
或者你可以把它变成一个全局的,并且只得到单例的一个的缺点。使用单例,您可以同时将自己限制为该数据库类的一个实例。为什么要这样做?或者您可以看看为什么您有如此多的依赖项以至于实例化列表变得“非常长”。它们都是必需的吗?是否应该将其中一些委派给其他组件?也许其中一些可以打包在一个结构中,这样我们就可以将它们作为单个参数传递。有很多解决方案,它们都比单例更好。 是的,单身人士可能在那里是合理的。但我认为你刚刚证明了我的观点,即只有在非常奇特的情况下才有必要。大多数软件不处理除雪硬件。但我仍然不相信。我同意在您的实际应用程序中,您只需要其中之一。但是你的单元测试呢?它们中的每一个都应该独立运行,因此理想情况下它们应该创建自己的 SpreaderController——这对于单例来说很难做到。最后,为什么您的同事首先要创建多个实例?这是一个需要防范的现实场景吗? 您错过的一点是,尽管您的最后两个示例可以证明“只有一个实例”的限制是合理的,但它们并不能证明“全局可访问”的限制是合理的。为什么整个代码库都应该能够访问您的电话交换机的管理单元?单例的重点是为您提供 both 特征。如果您只需要其中一个,则不应使用单例。 @ jalf - 我的目标只是给你一个例子,说明单例在野外的用处,因为你无法想象;我想您不会多次看到将其应用到您当前的工作中。我从业务应用程序切换到扫雪机编程只是因为它允许我使用 Singleton。 :) j/k 我同意你的前提,即有更好的方法来做这些事情,你给了我很多思考。感谢您的讨论! 使用单例 (AHEM!) “模式”来阻止人们实例化更多实例是非常愚蠢的,只是为了防止人们偶然这样做。当我在我的小函数中有一个 Foo 类型的局部变量 foo1 并且只想要一个在函数中时,我不担心有人会创建第二个 Foo 变量 foo2 并使用它而不是原来的那个。【参考方案3】:单例的问题不在于它们的实现。而是它们将两个不同的概念混为一谈,显然这两个概念都不可取。
1) 单例提供了对对象的全局访问机制。尽管它们在没有明确定义的初始化顺序的语言中可能稍微更线程安全或更可靠,但这种用法仍然是全局变量的道德等价物。它是一个用一些笨拙的语法修饰的全局变量(比如 foo::get_instance() 而不是 g_foo),但它的用途完全相同(整个程序都可以访问单个对象)并且具有完全相同的缺点。
2) 单例防止类的多个实例化。很少见,IME,这种特性应该被纳入一个类。这通常是一个更具上下文的事情。许多被认为是唯一的事情实际上只是碰巧是唯一的。 IMO 更合适的解决方案是只创建一个实例——直到您意识到您需要多个实例。
【讨论】:
同意。在现实世界中,根据某些人的说法,两个错误可能会成为正确。但在编程中,将两个坏主意混在一起不会产生一个好主意。【参考方案4】:模式有一点:不要一概而论。它们有所有有用的情况,也有失败的情况。
当您必须测试代码时,单例可能会令人讨厌。您通常会被类的一个实例卡住,并且可以选择在构造函数中打开一扇门或使用某种方法来重置状态等等。
另一个问题是,Singleton 实际上只不过是一个变相的全局变量。当你的程序有太多的全局共享状态时,事情往往会倒退,我们都知道。
这可能会使依赖跟踪变得更加困难。当一切都取决于你的单例时,更难改变它,分成两个,等等。你通常会坚持下去。这也妨碍了灵活性。研究一些依赖注入框架来尝试缓解这个问题。
【讨论】:
不,单例不仅仅是伪装的全局变量。这就是它特别糟糕的原因。它将全局性(通常是不好的)与另一个也不好的概念(即如果程序员决定他需要一个实例,则不让他实例化一个类)它们通常是 使用 作为全局变量,是的。然后他们还拖累了另一个令人讨厌的副作用,并削弱了代码库。 还应该注意,单例不必具有公共可访问性。单例可以很好地位于库的内部,并且永远不会暴露给用户。所以从这个意义上说,它们不一定是“全球性的”。 @jalf 不允许某人创建多个类的实例并不是一件坏事。如果真的只有一个实例化的类实例来强制要求。如果有人后来决定他们需要创建另一个实例,他们应该重构它,因为它本来就不应该是一个单例。 @William:一旦某个东西变成了单例,重构就变得异常困难。现在,您能否给我一个单一的理由,说明为什么强制执行这种“仅一个实例”限制是个好主意? 一个无疑是正确做法的例子? @William:我不得不时不时地拥有多个记录器。您不是在为单身人士争论,而是在为一个普通的老当地人争论。您想知道 a 记录器始终可用。这就是全局的用途。您不需要知道不可能实例化其他记录器,这是单例强制执行的。 (尝试为您的记录器编写单元测试——如果您可以根据需要创建和销毁它,这会容易得多,而这对于单例来说是不可能的)【参考方案5】:单例基本上让您在语言中拥有复杂的全局状态,否则很难或不可能拥有复杂的全局变量。
Java 尤其使用单例来代替全局变量,因为所有东西都必须包含在一个类中。与全局变量最接近的是公共静态变量,可以像使用import static
一样使用全局变量
C++ 确实有全局变量,但调用全局类变量的构造函数的顺序是未定义的。因此,单例允许您将全局变量的创建推迟到第一次需要该变量时。
Python 和 Ruby 等语言很少使用单例,因为您可以在模块中使用全局变量。
那么什么时候使用单例是好/坏?几乎确切地说,何时使用全局变量是好/坏。
【讨论】:
全局变量何时是“好”的?有时它们是解决问题的最佳解决方法,但它们从来都不是“好”。 全局变量用在任何地方都很好,任何东西都可以访问它。单状态图灵机的实现可以使用单例。 我喜欢这个答案中的间接层:“何时使用全局变量是好/坏”。 DevSolar 和 Lee Louviere 都获得了他们同意的价值,尽管在回答时无法知道谁会发表评论。【参考方案6】:Modern C++ Design by Alexandrescu 有一个线程安全、可继承的泛型单例。
对于我的 2p 价值,我认为为你的单身人士定义生命周期很重要(当绝对有必要使用它们时)。我通常不会让静态get()
函数实例化任何东西,并将设置和销毁留给主应用程序的某个专用部分。这有助于突出单例之间的依赖关系 - 但是,如上所述,最好尽可能避免它们。
【讨论】:
【参考方案7】:如何正确实现单例
有一个我从未见过的问题,是我在上一份工作中遇到的。我们有在 DLL 之间共享的 C++ 单例,并且确保类的单个实例的通常机制不起作用。问题是每个 DLL 都有自己的一组静态变量以及 EXE。如果您的 get_instance 函数是内联函数或静态库的一部分,则每个 DLL 都会以自己的“单例”副本结束。
解决方案是确保单例代码仅在一个 DLL 或 EXE 中定义,或者创建一个具有这些属性的单例管理器来打包实例。
【讨论】:
老兄,听说你喜欢单例,所以我为你的单例做了一个单例,这样你就可以在反模式的同时反模式。 @Eva,是这样的。问题不是我造成的,我只是想办法让它发挥作用。【参考方案8】:第一个示例不是线程安全的 - 如果两个线程同时调用 getInstance,则该静态将是一个 PITA。某种形式的互斥锁会有所帮助。
【讨论】:
是的,在上面的 cmets 中注明:* 限制:单线程设计 * 请参阅:aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf * 与多线程应用程序中的锁定相关的问题 经典的单例只有 getInstance 作为静态方法和其他操作的实例方法永远不能成为线程安全的。 (好吧,除非您使用线程本地存储将其设为每线程单吨...) 即使在 c++11 或更高版本中?【参考方案9】:正如其他人所指出的,单例的主要缺点包括无法扩展它们,以及失去实例化多个实例的能力,例如用于测试目的。
单例的一些有用方面:
-
延迟或预先实例化
适用于需要设置和/或状态的对象
但是,您不必使用单例来获得这些好处。您可以编写一个正常的对象来完成这项工作,然后让人们通过工厂(一个单独的对象)访问它。如果需要,工厂可以担心只实例化一个,然后重用它等。此外,如果您对接口而不是具体类进行编程,工厂可以使用策略,即您可以切换接口的各种实现。
最后,工厂适用于 Spring 等依赖注入技术。
【讨论】:
【参考方案10】:当您在初始化和对象时运行大量代码时,单例非常方便。例如,当您在设置持久性对象时使用 iBatis 时,它必须读取所有配置、解析映射、确保其全部正确等等,然后才能访问您的代码。
如果您每次都这样做,性能会大大降低。在单例中使用它,你只需要一次,然后所有后续调用都不必这样做。
【讨论】:
Prototype Pattern 也能做到这一点,而且更加灵活。当您的客户将创建许多昂贵类的实例时,您也可以使用它,但实际上只有有限数量的实例具有不同的状态。例如,俄罗斯方块中的 tetronimos。【参考方案11】:单例的真正失败在于它们破坏了继承。除非您有权访问引用 Singleton 的代码,否则您无法派生新类来为您提供扩展功能。因此,除了 Singleton 将使您的代码紧密耦合(可通过策略模式修复......也称为依赖注入)这一事实之外,它还将阻止您关闭代码部分的修订(共享库)。
所以即使是记录器或线程池的例子也是无效的,应该用 Strategies 代替。
【讨论】:
记录器本身不应该是单例。一般的“广播”消息系统应该是。记录器本身就是广播消息的订阅者。 线程池也不应该是单例的。一般的问题是你会想要不止一个吗?是的。当我上次使用它们时,我们在一个应用程序中有 3 个不同的线程池。【参考方案12】:大多数人在试图让自己对使用全局变量感觉良好时会使用单例。有合法用途,但在大多数情况下,人们使用它们时,与全局可访问的事实相比,只能有一个实例这一事实只是微不足道的事实。
【讨论】:
【参考方案13】:因为单例只允许创建一个实例,所以它有效地控制了实例复制。例如,您不需要查找的多个实例 - 例如莫尔斯查找映射,因此将其包装在单例类中是恰当的。仅仅因为你有一个类的实例并不意味着你也受到对该实例的引用数量的限制。您可以对实例的调用进行排队(以避免线程问题)并进行必要的更改。是的,单例的一般形式是全局公共的,您当然可以修改设计以创建更多访问受限的单例。我以前没有厌倦过这个,但我确定这是可能的。 对于所有评论说单例模式完全邪恶的人,你应该知道这一点:是的,如果你没有正确使用它或者在它的有效功能和可预测的行为范围内使用它是邪恶的:不要泛化。
【讨论】:
【参考方案14】:但是当我需要像 Singleton 这样的东西时,我通常最终会使用 Schwarz Counter 来实例化它。
【讨论】:
【参考方案15】:下面是实现线程安全单例模式的更好方法,它在析构函数本身中释放内存。但我认为析构函数应该是可选的,因为当程序终止时,单例实例将被自动销毁:
#include<iostream>
#include<mutex>
using namespace std;
std::mutex mtx;
class MySingleton
private:
static MySingleton * singletonInstance;
MySingleton();
~MySingleton();
public:
static MySingleton* GetInstance();
MySingleton(const MySingleton&) = delete;
const MySingleton& operator=(const MySingleton&) = delete;
MySingleton(MySingleton&& other) noexcept = delete;
MySingleton& operator=(MySingleton&& other) noexcept = delete;
;
MySingleton* MySingleton::singletonInstance = nullptr;
MySingleton::MySingleton() ;
MySingleton::~MySingleton()
delete singletonInstance;
;
MySingleton* MySingleton::GetInstance()
if (singletonInstance == NULL)
std::lock_guard<std::mutex> lock(mtx);
if (singletonInstance == NULL)
singletonInstance = new MySingleton();
return singletonInstance;
关于我们需要使用单例类的情况可以是—— 如果我们想在程序的整个执行过程中保持实例的状态 如果我们涉及写入应用程序的执行日志,其中只需要使用文件的一个实例......等等。 如果有人可以在我上面的代码中提出优化建议,那将是非常可观的。
【讨论】:
那绝对不是更好。 1:您没有使用指针定义所有权语义。除非你准备好管理指针,否则你不应该在 C++ 中使用指针。 2:您对双重检查锁定的使用已经过时,并且有更好的现代方法来做到这一点。 3:你对破坏的cmet很天真。内存的回收不是析构函数使用的重点,而是清理。更好版本的建议:看问题。那里展示的版本已经好多了。【参考方案16】:我使用单身人士作为面试测试。
当我让开发人员说出一些设计模式时,如果他们只能说出 Singleton,他们不会被录用。
【讨论】:
严格而快速的招聘规则会让你错过各种各样的潜在员工。 存在各种各样的白痴。这并不意味着他们应该被考虑招聘。如果有人可以完全不提及设计模式,我认为他们会比知道单例且没有其他模式的人更可取。 对于记录簿 - 我的回答是开玩笑的。在我的实际面试过程中,我会尝试评估我们是否需要辅导 C++ 的人,以及这会有多难。我最喜欢的一些候选人是完全不了解 C++ 的人,但我能够与他们进行精彩的交谈。 投反对票。根据我的个人经验 - 程序员可能无法命名除单例之外的任何其他模式,但这并不意味着他使用单例。就个人而言,在我听说过它们之前,我在我的代码中使用了单例(我称它们为“更智能的全局变量”——我知道全局变量是什么)。当我了解它们时,当我了解它们的优缺点时 - 我停止使用它们。突然,当我停下来时,单元测试对我来说变得更有趣了……这会让我成为一个更糟糕的程序员吗?噗…… 我也反对“命名一些设计模式”的废话问题。设计是关于理解如何应用设计模式,而不仅仅是能够说出他们的名字。好的,这可能不值得反对,但这个答案是巨魔。【参考方案17】:当我有一个封装大量内存的类时,我发现它们很有用。例如,在我最近开发的一个游戏中,我有一个影响图类,其中包含一组非常大的连续内存数组。我希望在启动时全部分配,在关机时全部释放,我绝对只想要一份副本。我还必须从很多地方访问它。我发现单例模式在这种情况下非常有用。
我确信还有其他解决方案,但我发现这个非常有用且易于实施。
【讨论】:
【参考方案18】:反使用:
过度使用单例的一个主要问题是该模式阻止了替代实现的轻松扩展和交换。无论在何处使用单例,类名都是硬编码的。
【讨论】:
被否决的原因有两个:1. Singleton 可以在内部使用多态实例(例如,全局 Logger 使用多态的定位策略) 2. 单例名称可以有 typedef,所以代码实际上依赖于 typedef。 我最终构建了我的单例版本,以便使用奇怪的重复模板模式进行扩展。【参考方案19】:我认为这是 C# 最强大的版本:
using System;
using System.Collections;
using System.Threading;
namespace DoFactory.GangOfFour.Singleton.RealWorld
// MainApp test application
class MainApp
static void Main()
LoadBalancer b1 = LoadBalancer.GetLoadBalancer();
LoadBalancer b2 = LoadBalancer.GetLoadBalancer();
LoadBalancer b3 = LoadBalancer.GetLoadBalancer();
LoadBalancer b4 = LoadBalancer.GetLoadBalancer();
// Same instance?
if (b1 == b2 && b2 == b3 && b3 == b4)
Console.WriteLine("Same instance\n");
// All are the same instance -- use b1 arbitrarily
// Load balance 15 server requests
for (int i = 0; i < 15; i++)
Console.WriteLine(b1.Server);
// Wait for user
Console.Read();
// "Singleton"
class LoadBalancer
private static LoadBalancer instance;
private ArrayList servers = new ArrayList();
private Random random = new Random();
// Lock synchronization object
private static object syncLock = new object();
// Constructor (protected)
protected LoadBalancer()
// List of available servers
servers.Add("ServerI");
servers.Add("ServerII");
servers.Add("ServerIII");
servers.Add("ServerIV");
servers.Add("ServerV");
public static LoadBalancer GetLoadBalancer()
// Support multithreaded applications through
// 'Double checked locking' pattern which (once
// the instance exists) avoids locking each
// time the method is invoked
if (instance == null)
lock (syncLock)
if (instance == null)
instance = new LoadBalancer();
return instance;
// Simple, but effective random load balancer
public string Server
get
int r = random.Next(servers.Count);
return servers[r].ToString();
这是 .NET 优化版本:
using System;
using System.Collections;
namespace DoFactory.GangOfFour.Singleton.NETOptimized
// MainApp test application
class MainApp
static void Main()
LoadBalancer b1 = LoadBalancer.GetLoadBalancer();
LoadBalancer b2 = LoadBalancer.GetLoadBalancer();
LoadBalancer b3 = LoadBalancer.GetLoadBalancer();
LoadBalancer b4 = LoadBalancer.GetLoadBalancer();
// Confirm these are the same instance
if (b1 == b2 && b2 == b3 && b3 == b4)
Console.WriteLine("Same instance\n");
// All are the same instance -- use b1 arbitrarily
// Load balance 15 requests for a server
for (int i = 0; i < 15; i++)
Console.WriteLine(b1.Server);
// Wait for user
Console.Read();
// Singleton
sealed class LoadBalancer
// Static members are lazily initialized.
// .NET guarantees thread safety for static initialization
private static readonly LoadBalancer instance =
new LoadBalancer();
private ArrayList servers = new ArrayList();
private Random random = new Random();
// Note: constructor is private.
private LoadBalancer()
// List of available servers
servers.Add("ServerI");
servers.Add("ServerII");
servers.Add("ServerIII");
servers.Add("ServerIV");
servers.Add("ServerV");
public static LoadBalancer GetLoadBalancer()
return instance;
// Simple, but effective load balancer
public string Server
get
int r = random.Next(servers.Count);
return servers[r].ToString();
你可以在dotfactory.com找到这个模式。
【讨论】:
您可以去掉与 Singleton 无关的部分,以使代码更易于阅读。 另外,您的第一个版本不是线程安全的,因为可能存在读/写重新排序。见***.com/questions/9666/… 呃……语言错误?这个问题很明显被标记为 C++。【参考方案20】:Meyers 单例模式在大多数情况下都可以很好地工作,在某些情况下,寻找更好的东西并不一定值得。只要构造函数永远不会抛出并且单例之间没有依赖关系。
单例是全局可访问对象(从现在开始为 GAO)的实现,尽管并非所有 GAO 都是单例。
记录器本身不应该是单例,但理想的记录方式应该是全局可访问的,以便将生成日志消息的位置与记录的位置或方式分离。
延迟加载/延迟评估是一个不同的概念,单例通常也会实现它。它有很多自己的问题,特别是线程安全和如果它因异常而失败时的问题,以至于当时看起来是个好主意,结果却不是那么好。 (有点像字符串中的 COW 实现)。
考虑到这一点,GOA 可以这样初始化:
namespace
T1 * pt1 = NULL;
T2 * pt2 = NULL;
T3 * pt3 = NULL;
T4 * pt4 = NULL;
int main( int argc, char* argv[])
T1 t1(args1);
T2 t2(args2);
T3 t3(args3);
T4 t4(args4);
pt1 = &t1;
pt2 = &t2;
pt3 = &t3;
pt4 = &t4;
dostuff();
T1& getT1()
return *pt1;
T2& getT2()
return *pt2;
T3& getT3()
return *pt3;
T4& getT4()
return *pt4;
它不需要像那样粗暴地完成,并且显然在包含对象的加载库中,您可能需要一些其他机制来管理它们的生命周期。 (将它们放在加载库时获得的对象中)。
至于我什么时候使用单例?我用它们做了两件事 - 一个单例表,指示已使用 dlopen 加载了哪些库 - 记录器可以订阅并且您可以向其发送消息的消息处理程序。信号处理程序特别需要。
【讨论】:
【参考方案21】:我仍然不明白为什么单例必须是全局的。
我打算生成一个单例,我将一个数据库作为私有常量静态变量隐藏在类中,并制作利用数据库的类函数,而无需将数据库暴露给用户。
我不明白为什么这个功能会不好。
【讨论】:
我不明白你为什么认为它必须是全球性的。 根据这个帖子,每个人都在说单例必须是全局的 没有。该线程指示单例具有全局状态。并不是说它是一个全局变量。您提出的解决方案具有全局状态。您提出的解决方案也是使用全局变量;类的静态成员是“静态存储持续时间”的对象,全局变量是“静态存储持续时间”的对象。因此,这两者基本上是相同的东西,语义/范围略有不同。 那么由于“静态存储持续时间”,私有静态变量仍然是全局变量? 注意:你错过了我故意没有说明的位。您使用静态“私人”成员的设计与单例一样不错。因为它没有引入“全局可变状态”。但它也不是单例。单例是一个被设计成只能存在一个对象实例的类。您建议的是一个类的所有对象的单一共享状态。不同的概念。【参考方案22】:如果您是创建单例并使用它的人,请不要将其设为单例(这没有意义,因为您可以控制对象的奇异性而不使其成为单例)但是当您使用库的开发者,并且您只想向您的用户提供一个对象(在这种情况下,您是创建单例的人,但您不是用户)。
单例是对象,所以将它们用作对象,许多人直接通过调用返回它的方法来访问单例,但这是有害的,因为您让您的代码知道对象是单例,我更喜欢将单例用作对象,我将它们传递给构造函数并将它们用作普通对象,通过这种方式,您的代码不知道这些对象是否是单例,这使得依赖关系更加清晰,并且对重构有点帮助......
【讨论】:
【参考方案23】:在桌面应用程序中(我知道,只有我们恐龙才写这些!)它们对于获得相对不变的全局应用程序设置至关重要 - 用户语言、帮助文件的路径、用户偏好等,否则这些设置必须传播到每个类以及每个对话。
编辑 - 当然这些应该是只读的!
【讨论】:
但这是在乞求问题;为什么用户语言和帮助文件的路径必须是实例方法根本? 我们为此提供了全局变量。没有必要让它们成为单例 全局变量——那么你如何从注册表/数据库中序列化它们?全局类 - 那么你如何确保只有其中一个? @mgb:您通过从注册表/数据库读取值并将它们存储在全局变量中来序列化它们(这可能应该在主函数的顶部完成)。你确保只有一个类的对象,通过只创建一个类的对象......真的......很难 'grep -rn "new \+global_class_name" 。 ?真的吗? @mgb:我到底为什么要确保只有一个?我只需要知道一个实例总是代表 current 设置。但是没有理由不允许我在这个地方有其他设置对象。例如,可能是“用户当前正在定义但尚未应用的设置”。或者一个用于“用户之前保存的配置,以便他以后可以返回到他们”。或者每个单元测试一个。【参考方案24】:另一种实现
class Singleton
public:
static Singleton& Instance()
// lazy initialize
if (instance_ == NULL) instance_ = new Singleton();
return *instance_;
private:
Singleton() ;
static Singleton *instance_;
;
【讨论】:
这真是太可怕了。请参阅:***.com/questions/1008019/c-singleton-design-pattern/… 以获得更好的延迟初始化和更重要的保证确定性销毁。 如果你要使用指针,Instance()
应该返回一个指针,而不是一个引用。在您的 .cpp
文件中,将实例初始化为 null:Singleton* Singleton::instance_ = nullptr;
。而Instance()
应实现为:if (instance_ == nullptr) instance_ = new Singleton(); return instance_;
。以上是关于单例:应该如何使用的主要内容,如果未能解决你的问题,请参考以下文章
在 C++ 中,如何使用单例来确保每个类都有唯一的整数 ID?