Singleton:应如何使用


291

编辑:从另一个问题中,我提供了一个答案,该答案具有许多有关单身人士的问题/答案的链接:有关单身人士的更多信息,请参见:

因此,我读了Singletons主题:好的设计还是拐杖?
而且争论仍然很激烈。

我认为单例是一种设计模式(好的和坏的)。

Singleton的问题不是模式,而是用户(对不起每个人)。每个人和他们的父亲都认为他们可以正确实施一个方案(从我进行的许多访谈中,大多数人都做不到)。同样因为每个人都认为他们可以实现正确的Singleton,所以他们滥用Pattern并在不合适的情况下使用它(用Singletons代替全局变量!)。

因此,需要回答的主要问题是:

  • 什么时候应该使用Singleton
  • 您如何正确实现Singleton

我对本文的希望是,我们可以在一个地方(而不是谷歌和搜索多个站点)一起收集何时(然后如何)正确使用Singleton的权威来源。同样合适的是反使用和常见的不良实现的列表,这些列表解释了为什么它们无法正常工作以及对于好的实现而言它们的弱点。


所以滚了一下球:
我会举起我的手,说这是我用的,但可能有问题。
我喜欢他的“有效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;
        }
};

好。让我们将一些批评和其他实现放在一起。
:-)


36
如果您以后决定要多个记录器怎么办?还是多个线程池?如果只需要一个记录器,则只需创建一个实例并将其设置为全局。只有您绝对需要只有一个,并且需要全球化,恕我直言,单身人士才是好的。

3
谁说一个框架只能有1个记录器实例。一个代表框架的singelton。然后,Framwork可以为您提供特定的记录器。
马丁·约克

是的 我不会使用singeltong作为线程池。只是抛出想法来激发答案。
马丁·约克2009年

@Dan Singleton实现策略模式。行为是从单例中抽象出来的。Singleton是一个入口点。没有两个记录器,只有一个记录器可以决定如何记录。您不能一次只输出到一个日志,而不必一次输出两个。
Lee Louviere

5
Xaade:如果要登录两个文件怎么办?还是数据库?还是网络插座?还是GUI小部件?重点是,请勿添加人为限制-无需这样做。您有多少次不小心创建了两个for循环而不是一个?如果您只想要一个记录器,则仅创建一个。

Answers:


181

你们所有人都是错的。阅读问题。回答:

在以下情况下使用单例:

  • 您在系统中仅需要一个类型的对象

在以下情况下,请勿使用Singleton:

  • 您想节省内存
  • 你想尝试一些新东西
  • 你想炫耀你知道多少
  • 因为其他所有人都在这样做(请参阅Wikipedia中的cult程序员
  • 在用户界面小部件中
  • 应该是缓存
  • 在字符串中
  • 会话中
  • 我可以整天去

如何创建最佳单身人士:

  • 越小越好。我是一个极简主义者
  • 确保它是线程安全的
  • 确保它永远不会为空
  • 确保仅创建一次
  • 延迟还是系统初始化?根据您的要求
  • 有时OS或JVM为您创建单例(例如,在Java中,每个类定义都是一个单例)
  • 提供一个析构函数或以某种方式弄清楚如何处置资源
  • 占用很少的内存

14
实际上,我认为您也不太正确。我的意思是:“如果系统中只需要一个类型的对象,并且需要全局访问它,则需要强调的是我的-如果方便,不要这样做,只有当你必须拥有它。

91
你也是错的 如果只需要一个对象,则只需创建一个。如果没有逻辑方法可以容纳两个实例而不会不可逆地损坏应用程序,则应考虑将其单例化。还有另一个方面,全局访问:如果您不需要对实例的全局访问,则不应是单例。
jalf

4
关闭进行修改,打开进行扩展。问题是您不能将单身人士扩展为双人或三人。它作为一个单例卡住。
Lee Louviere

2
@ enzom83:大写的S Singleton包含确保其唯一性的代码。如果您只想要一个实例,则可能会丢失该代码并仅自己创建一个实例...为您节省了单个实例的内存,再加上避免了执行单一性代码的节省,这也意味着不牺牲如果您的需求发生变化,可以创建第二个实例。
cHao 2014年

4
“如果您在系统中只需要一个类型的对象,那么–……从不希望在单元测试中模拟该对象。”
Cygon 2014年

72

单身人士使您能够在一个班级中结合两个不良特征。在几乎所有方面这都是错误的。

单例可以为您提供:

  1. 全局访问对象,以及
  2. 保证最多只能创建一个这种类型的对象

第一很简单。全球人通常是坏的。除非我们确实需要,否则绝不应该使对象在全局范围内可访问。

第二个听起来似乎很有意义,但让我们考虑一下。您上次“偶然地” *创建新对象而不是引用现有对象是什么时候?由于此标记为C ++,因此让我们使用该语言的示例。你经常不小心写吗

std::ostream os;
os << "hello world\n";

当你打算写的时候

std::cout << "hello world\n";

当然不是。我们不需要针对此错误的保护措施,因为这种错误不会发生。如果是这样,正确的反应是回家睡觉12至20个小时,希望您感觉好些。

如果只需要一个对象,则只需创建一个实例。如果一个对象可以全局访问,则使其成为全局对象。但这并不意味着就不可能创建它的其他实例。

“只有一个实例是可能的”约束并不能真正保护我们免受可能的错误侵害。但这确实使我们的代码很难重构和维护。因为我们以后经常发现我们确实需要多个实例。我们这样做有一个以上的数据库,我们有一个以上的配置对象,我们希望几个伐木工人。举一个常见的例子,我们的单元测试可能希望能够在每个测试中创建和重新创建这些对象。

因此,一个单应当且仅当被使用,我们需要这两个它提供的特性:如果我们需要全球性的访问(这是罕见的,因为全局一般气馁)我们需要防止任何人曾经创造的多个实例类(在我看来,这是一个设计问题)。我能看到的唯一原因是,如果创建两个实例会破坏我们的应用程序状态-可能是因为该类包含许多静态成员或类似的愚蠢行为。在这种情况下,显而易见的答案是修复该类。它不应该依赖于唯一的实例。

如果您需要全局访问对象,请将其设为全局对象,例如std::cout。但不要限制可以创建的实例数量。

如果绝对是肯定的,那么您肯定需要将一个类的实例数限制为一个,并且绝对不可能安全地创建第二个实例,然后强制执行。但是也不要使其在全球范围内都可访问。

如果您确实需要两个特征,那么1)使其成为一个单例,2)让我知道您的需求,因为我很难想象这样的情况。


3
或者你可以使它成为一个全球性的,只能得到一个单身的缺点的。使用单例,您将同时将自己限制在该数据库分类的一个实例中。为什么这样 或者,您可以查看为什么您有如此多的依赖关系,以至于实例化列表变得“真的很长”。它们都是必要的吗?是否应该将其中一些委托给其他组件?也许可以将它们中的一些打包为一个结构,以便我们将它们作为单个参数传递。有很多解决方案,所有解决方案都比单例更好。
jalf

6
是的,在那里可能有一个单例是合理的。但是我认为您已经证明了我的观点,即只有在非常特殊的情况下才有必要。大多数软件不处理耕雪硬件。但是我仍然不相信。我同意在您的实际应用中,您只需要其中之一。但是您的单元测试呢?它们中的每一个都应该独立运行,因此理想情况下,它们应该创建自己的SpreaderController-单身很难做到。最后,为什么您的同事首先要创建多个实例?那是一个现实的情况可以防范吗?
jalf

3
您可能遗漏的一点是,尽管最后两个示例可以证明“仅一个实例”的限制是合理的,但它们却无济于事,可以证明“全局可访问”的限制是合理的。为什么在整个地球上,整个代码库都应该能够访问电话交换机的管理单元?单身人士的要点是要给你两个特征。如果只需要一个,则不要使用单例。
jalf

2
@ jalf-我的目标只是给您一个示例,说明Singleton在野外有用的地方,因为您无法想象。我想您不会看到很多次将其应用于当前的工作范围。我从业务应用程序切换到扫雪机编程,完全是因为它可以让我使用Singleton。:) j / k我同意您的前提,那就是有更好的方法来做这些事情,您已经给了我很多思考的机会。感谢您的讨论!
J. Polfer,2009年

2
使用单例(AHEM!)“模式”来防止人们实例化更多实例是普通的愚蠢做法,只是为了防止人们偶然地这样做。当我在小函数中有一个Foo类型的局部变量foo1并且只想要一个函数时,我不担心有人会创建第二个Foo变量foo2而不是原始变量。
Thomas Eding

36

单例的问题不是它们的实现。正是它们将两个不同的概念混为一谈,这两个概念显然都不可取。

1)单例为对象提供全局访问机制。尽管在没有明确定义的初始化顺序的语言中,它们在线程安全性上可能更高一些,在某种程度上也更可靠,但这种用法仍然在道德上等同于全局变量。这是一个用一些笨拙的语法(例如foo :: get_instance()而不是g_foo)修饰的全局变量,但是它具有完全相同的目的(可以在整个程序中访问单个对象),并且具有完全相同的缺点。

2)单例阻止一个类的多个实例化。IME很少将这种功能归为一类。通常,这是上下文相关的事情;许多被认为是唯一的事情实际上只是发生在唯一的事情上。IMO一个更合适的解决方案是仅创建一个实例-直到您意识到需要多个实例为止。


6
同意 在现实世界中,某些人可能会犯两个错误。但是在编程中,将两个坏主意混合在一起不会产生一个好主意。
jalf

27

模式的一件事:不要泛化。在所有情况下,当它们有用时都会失败。

当您必须测试代码时,Singleton可能会令人讨厌。通常,您只能使用该类的一个实例,并且可以选择在构造函数中打开一扇门,也可以选择某种方法来重置状态,依此类推。

另一个问题是,单例实际上只是变相的全局变量。当您的程序具有太多的全局共享状态时,情况往往会倒退,我们都知道。

这可能会使依赖项跟踪更加困难。当一切都取决于您的Singleton时,很难更改它,将其拆分为两个,等等。通常,您会受其困扰。这也会影响灵活性。研究一些依赖注入框架,以尝试缓解此问题。


8
不,单身汉不仅仅是一个变相的全局变量。这就是使它特别糟糕的原因。它结合了全球岬(通常是坏的)与另一个概念,它是不错(如果他决定他需要一个实例不让程序员实例化一个类的),它们通常使用全局变量,是的。然后他们也拖入了另一个讨厌的副作用,并削弱了代码库。
jalf

7
还应注意,单身人士不必具有公共可访问性。单例可以很好地位于库的内部,并且永远不会暴露给用户。因此,它们在这种意义上不一定是“全局的”。
史蒂文·埃弗斯

1
+1指出Singletons正在测试的痛苦程度。
DevSolar 2011年

1
@jalf不允许某人创建一个类的多个实例不是一件坏事。如果确实只有一个实例化的类实例可以强制执行该要求。如果以后有人决定需要创建另一个实例,则应该对其进行重构,因为它首先不应成为单例。
威廉

2
@William:而且我不得不不时拥有多个记录器。您不是在为单身人士而争吵,而是为一个普通的老当地人争吵。你要知道,一个记录器始终可用。那就是全球性的目的。您不需要知道不可能实例化任何其他记录器,这是单例强制执行的。(尝试为记录器编写单元测试–如果您可以根据需要创建和销毁它,这会容易得多,而且单例操作是不可能的)
jalf 2012年

13

单例基本上使您可以使用多种语言来拥有复杂的全局状态,否则它们将很难或不可能具有复杂的全局变量。

尤其是Java,因为所有内容都必须包含在一个类中,所以它使用单例代替全局变量。与全局变量最接近的是公共静态变量,可以将其用作全局静态变量。import static

C ++确实具有全局变量,但是全局类变量的构造函数的调用顺序是不确定的。这样,单例可让您将全局变量的创建推迟到第一次需要该变量时进行。

诸如Python和Ruby之类的语言很少使用单例,因为您可以在模块内使用全局变量。

那么什么时候使用单例是好是坏?几乎完全可以确定使用全局变量的时机。


什么时候全局变量是“好”?有时,它们是解决问题的最佳方法,但它们从来都不是“好”的。
DevSolar 2011年

1
全局变量在任何地方都可以使用时,它是一个好方法,并且所有人都可以使用它。单状态图腾机的实现可以利用单例。
Lee Louviere

我喜欢这个答案中的间接层:“何时使用全局变量是好事还是坏事”。DevSolar和Lee Louviere都获得了他们所认同的价值,即使在回答时尚不清楚谁会发表评论。
Praxeolitic

6

Alexandrescu编写的Modern C ++ Design具有线程安全的,可继承的通用单例。

对于我的2p值,我认为为单身人士定义生存期非常重要(当绝对必要时,要使用它们)。我通常不让静态get()函数实例化任何东西,而将设置和销毁工作留给主应用程序的某些专用部分。这有助于突出显示单例之间的依赖性-但是,如上所述,最好尽可能避免它们。


6
  • 您如何正确实现Singleton

我从来没有提到过一个问题,这是我在上一份工作中遇到的。我们有在DLL之间共享的C ++单例,并且确保类的单个实例不起作用的通常机制。问题在于,每个DLL都有自己的一组静态变量以及EXE。如果您的get_instance函数是内联函数或静态库的一部分,则每个DLL都将带有其自己的“单例”副本。

解决方案是确保仅在一个DLL或EXE中定义单例代码,或者使用这些属性创建单例管理器以打包实例。


10
哟,我听说您喜欢Singleton,所以我为您的Singleton制作了Singleton,这样您就可以在反模式的同时反模式。
伊娃(Eva)2013年

@Eva,是这样的。我没有提出问题,只是必须使其以某种方式起作用。
Mark Ransom

5

第一个示例不是线程安全的-如果两个线程同时调用getInstance,则该静态对象将是PITA。某种形式的互斥锁会有所帮助。


是的,在上面的评论中指出:*限制:单线程设计*参见:aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf *有关与多线程应用程序中的锁定相关的问题
Martin York

仅将getInstance作为静态方法和用于其他操作的实例方法的经典单例永远无法确保线程安全。(好吧,除非您使用线程本地存储将其设置为每线程吨数...)
Tobi

即使在c ++ 11或更高版本中?
hg_git

5

正如其他人指出的那样,单例的主要缺点包括无法扩展它们,并失去了实例化多个实例(例如出于测试目的)的能力。

单例的一些有用方面:

  1. 惰性或前期实例化
  2. 方便用于需要设置和/或状态的对象

但是,您不必使用单例即可获得这些好处。您可以编写一个完成工作的普通对象,然后让人们通过工厂(单独的对象)访问它。如果需要,工厂可以担心仅实例化一个,然后重新使用它等。同样,如果您编程到接口而不是具体的类,则工厂可以使用策略,即可以切入和切出接口的各种实现。

最后,工厂适合采用Spring等依赖项注入技术。


3

当初始化和对象运行大量代码时,单例很方便。例如,当您使用iBatis设置持久性对象时,它必须读取所有配置,解析地图,确保其所有正确信息,等等。

如果您每次都这样做,则性能将大大降低。在单例中使用它时,只需击中一次,然后所有后续调用都不必这样做。


原型模式做到这一点为好,它更加灵活。当客户端将创建许多昂贵类的实例,但实际上只有少数几个实例具有不同的状态时,也可以使用它。例如,俄罗斯方块中的tetronimos。
伊娃

3

Singletons真正的失败之处在于它们破坏了继承。除非可以访问引用了Singleton的代码,否则不能派生新类来提供扩展功能。因此,除了Singleton可以使您的代码紧密耦合(可以通过策略模式... aka依赖注入而固定)之外,它还可以防止您关闭版本(共享库)中的代码部分。

因此,即使记录器或线程池的示例也是无效的,应将其替换为“策略”。


记录器本身不应该是单例。通用的“广播”消息系统应该如此。记录器本身是广播消息的订户。
CashCow 2011年

线程池也不应是单例。通常的问题是,您是否会想要其中之一以上?是。我上一次使用它们时,在一个应用程序中有3个不同的线程池。
CashCow 2011年

3

大多数人在尝试使自己对使用全局变量感到满意时会使用单例。存在合法用途,但是在大多数情况下,人们使用它们时,与只能在一个实例上访问的事实相比,只能存在一个实例只是一个琐碎的事实。


3

因为单例只允许创建一个实例,所以它有效地控制了实例复制。例如,您不需要多个查找实例-例如一个莫尔斯(Morse)查找图,因此将其包装在单例类中是很合适的。而且,仅因为您具有该类的单个实例,并不意味着您对该实例的引用数量也受到限制。您可以将对实例的调用排队(以避免线程问题)并影响必要的更改。是的,单例的一般形式是全局公开的,您当然可以修改设计以创建受访问限制更多的单例。我以前没有为此感到疲倦,但我肯定知道有可能。对于所有评论说单例模式完全是邪恶的人,您应该知道这一点:



2

下面是通过在析构函数本身中释放内存来实现线程安全单例模式的更好方法。但是我认为析构函数应该是可选的,因为当程序终止时,单例实例将被自动销毁:

#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;
}

关于我们需要使用单例类的情况,可以是-如果我们想在程序执行过程中保持实例的状态,如果我们只涉及写入应用程序的执行日志,而只需要一个文件实例,被使用...等等。如果有人可以在我上面的代码中建议优化,那将是很有意义的。


2
那绝对不是更好。1:您没有通过使用指针来定义所有权语义。除非您准备对其进行管理,否则切勿在C ++中使用指针。2:您对双重检查锁定的使用已过时,并且有更好的现代方法。3:您对破坏的评论是幼稚的。回收内存不是析构函数使用的重点,它是关于清除的。更好版本的建议:查看问题。那里展示的版本已经好得多了。
马丁·约克

1

我使用Singletons作为面试测试。

当我要求开发人员命名某些设计模式时,如果他们只能命名为Singleton,就不会雇用他们。


45
关于招聘的硬性规定会使您错过各种各样的潜在员工。
卡尔

13
有各种各样的白痴。这并不意味着应该考虑雇用他们。如果某人根本不提任何设计模式,那么我认为它们比知道单例的人更可取,而没有其他任何模式。
jalf

3
对于唱片,我的回答是嘲讽。在我实际的面试过程中,我尝试评估是否需要用C ++辅导某人,以及这样做的难度。我最喜欢的一些候选人是完全不了解C ++的人,但是我能够与他们进行很好的交谈。
马特·克鲁克香克2009年

4
否决票。根据我的个人经验-程序员可能无法命名Singleton以外的任何其他模式,但这并不意味着他使用Singletons。就个人而言,在我听说代码之前,我曾在代码中使用单例(我称它们为“更智能的全局变量”-我知道全局变量是什么)。当我了解他们时,当我了解他们的利弊时,我便停止使用它们。突然,当我停下来时,单元测试对我来说变得更加有趣了……这使我变得更糟糕了吗?Pfff ...
Paulius

3
我也对“命名某些设计模式”的废话不满意。设计是关于理解如何应用设计模式,而不仅仅是了解它们的名称。好的,那可能不值得一票,但这个答案是不义之举。
CashCow 2011年

0

反使用:

单例使用过多的一个主要问题是该模式阻止了易于扩展和替换其他实现。无论在何处使用单例,都对类别名称进行硬编码。


被拒绝的原因有两个:1. Singleton可以在内部使用多态实例(例如,全局Logger使用目标的多态策略)2. Singleton名称可以有typedef,因此代码实际上取决于typedef。
topright gamedev

我最终使用奇怪的重复模板模式构建了可扩展的单例版本。
Zachary Kraus

0

我认为这是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上找到此模式。


3
您可以删除与Singleton无关的部分,以使代码更易于阅读。
马丁·约克

同样,您的第一个版本也不是线程安全的,因为可能会进行读/写重新排序。见stackoverflow.com/questions/9666/...
托马斯Danecker

5
呃...语言错误?这个问题显然被标记为C ++
DevSolar 2011年

0

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已加载了哪些库-一个消息处理程序,记录器可以预订并向其发送消息。信号处理程序专门需要。


0

我仍然不明白为什么单身人士必须是全球性的。

我将要产生一个单例,在其中将类内的数据库隐藏为私有常量静态变量,并使类函数利用数据库,而无需将数据库暴露给用户。

我不明白为什么这个功能会不好。


我不明白您为什么认为它必须是全球性的。
马丁·约克

根据这个线索,每个人都在说一个单身必须是全球性的
Zachary Kraus 2014年

1
否。该线程表明一个singelton具有全局状态。并不是说它是一个全局变量。您提出的解决方案具有全局状态。您提出的解决方案也使用了全局变量。类的静态成员是“静态存储持续时间”的对象,全局变量是“静态存储持续时间”的对象。因此,两者基本上是相同的事物,但语义/作用域略有不同。
马丁·约克

那么由于“静态存储持续时间”,私有静态变量仍然是全局的吗?
Zachary Kraus 2014年

1
注意:您故意错过了我所说的所有内容。您使用静态“私有”成员的设计与singelton一样,也不错。因为它没有引入“全局可变状态”。但这也不是单身。单例是设计的类,因此只能存在该对象的一个​​实例。您建议的是类的所有对象的单一共享状态。不同的概念。
马丁·约克

0

当我有一个封装大量内存的类时,我发现它们很有用。例如,在我最近从事的游戏中,我有一个影响图类,其中包含非常大的连续内存数组的集合。我希望所有这些都在启动时分配,全部在关闭时释放,我绝对只想要它的一个副本。我还必须从许多地方访问它。我发现单例模式在这种情况下非常有用。

我敢肯定还有其他解决方案,但是我发现这一解决方案非常有用且易于实现。


0

如果您是创建单例并使用它的人,请不要将其作为单例(没有意义,因为您可以控制对象的奇异性而不使其成为单例),但是当您开发单例时,这才有意义库,并且您只想向用户提供一个对象(在这种情况下,您是创建单例的用户,但您不是用户)。

单例是对象,因此将它们用作对象,许多人通过调用返回单例的方法直接访问单例,但这是有害的,因为您使代码知道对象是单例,我更喜欢将单例用作对象,我将它们传递给通过构造函数,我将它们用作普通对象,这样,您的代码就不知道这些对象是否为单例,这使得依赖关系更加清晰,并且对重构有一点帮助...


-1

在台式机应用程序中(我知道,只有我们的恐龙会再写这些!),它们对于获得相对不变的全局应用程序设置至关重要-用户语言,帮助文件的路径,用户首选项等,否则它们必须传播到每个类和每个对话框中。

编辑-这些当然应该是只读的!


但这是一个问题。为什么用户语言和帮助文件的路径必须全部是instances方法?
DrPizza

2
我们有全球性。无需使它们单身
-jalf

全局变量-那么如何从注册表/数据库中序列化它们?地道课程-那么您如何确保只有一个?
马丁·贝克特

@mgb:通过从注册表/数据库中读取值并将它们存储在全局变量中来序列化它们(这可能应该在主函数的顶部完成)。通过仅创建类的一个对象,可以确保类中只有一个对象...真的...很难'grep -rn“ new \ + global_class_name”。?真?
Paulius

7
@mgb:为什么我要确保只有一个?我只需要知道一个实例始终代表当前设置即可。但是没有理由不容许我在该地点周围有其他设置对象。例如,可能是“用户当前正在定义但尚未应用的设置”之一。或“用户早些保存的配置,以便他以后可以返回”的一个选项。或每个单元测试一个。
jalf

-1

另一种实现

class Singleton
{
public:
    static Singleton& Instance()
    {
        // lazy initialize
        if (instance_ == NULL) instance_ = new Singleton();

        return *instance_;
    }

private:
    Singleton() {};

    static Singleton *instance_;
};

3
那真是太可怕了。请参阅:stackoverflow.com/questions/1008019/c-singleton-design-pattern/…,以获得更好的延迟初始化和更重要的是确保确定性销毁。
马丁·约克

如果要使用指针,Instance()应返回一个指针,而不是引用。里面的.cpp文件,初始化实例为null: Singleton* Singleton::instance_ = nullptr;。并Instance()应实现为:if (instance_ == nullptr) instance_ = new Singleton(); return instance_;
丹尼斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.