Singleton:如何使用它

时间:2008-09-17 19:17:40

标签: c++ design-patterns singleton

编辑:    从另一个问题我提供了一个答案,其中包含许多关于单身人士的问题/答案:More info about singletons here:

所以我已阅读帖子Singletons: good design or a crutch?
争论仍然激烈。

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

Singleton的问题不是模式而是用户(对不起所有人)。每个人和他们的父亲都认为他们可以正确地实施一个(而且从我做过的许多采访中,大多数人都做不到)。此外,因为每个人都认为他们可以实现正确的单身人士,他们滥用模式并在不合适的情况下使用它(用单身人士替换全局变量!)。

所以需要回答的主要问题是:

  • 什么时候应该使用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;
        }
};

行。让我们一起批评和其他实施。
: - )

24 个答案:

答案 0 :(得分:165)

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

在以下情况下使用单身人士:

  • 您需要在系统
  • 中拥有一个且只有一个类型的对象

如果出现以下情况,请不要使用Singleton:

  • 您想节省内存
  • 你想尝试新的东西
  • 你想炫耀你知道多少
  • 因为其他人都在这样做(请参阅维基百科中的cargo cult programmer
  • 在用户界面小部件中
  • 它应该是一个缓存
  • 字符串
  • 在会话中
  • 我可以整天待在那里

如何创建最佳单身人士:

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

答案 1 :(得分:70)

Singletons让您能够在一个类中组合两个不良特征。几乎在各方面都是错的。

单身人士给你:

  1. 对象的全局访问,
  2. 保证只能创建此类型
  3. 第一名很简单。全局通常都很糟糕。我们永远不应该让对象全局可访问,除非我们真的需要它。

    第二个听起来似乎有道理,但让我们考虑一下。你最后一次**意外*创建了一个新对象而不是引用一个现有对象是什么时候?由于这是标记的C ++,让我们使用该语言的一个例子。你经常不小心写

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

    当你打算写

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

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

    如果只需要一个对象,只需创建一个实例。如果一个对象应该可以全局访问,请将其设置为全局对象。但这并不意味着不可能创建它的其他实例。

    “只有一个实例可能”约束并不能真正保护我们免受可能的错误。但它确实使我们的代码很难重构和维护。因为我们经常发现以后我们确实需要多个实例。我们有多个数据库,我们有多个配置对象,我们确实需要几个记录器。我们的单元测试可能希望能够在每次测试时创建和重新创建这些对象,以便采用一个常见的例子。

    因此,当且仅当我们需要两者它提供的特征时才应该使用单例:如果我们需要全局访问(这是罕见的,因为全局变量通常是气馁)我们需要以阻止任何人永远创建多个类的实例(这听起来像是一个设计问题)。我能看到的唯一原因是,如果创建两个实例会破坏我们的应用程序状态 - 可能是因为该类包含许多静态成员或类似的愚蠢。在这种情况下,明显的答案是修复该类。它不应该依赖于唯一的实例。

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

    如果你绝对需要将一个类的实例数限制为一个,并且无法安全地处理创建第二个实例,那么就强制执行。但是也不要让它在全球范围内可访问。

    如果你确实需要这两种特性,那么1)让它成为单身,2)让我知道你需要什么,因为我很难想象这样的情况。

答案 2 :(得分:35)

单身人士的问题不是他们的实施。他们将两个不同的概念混为一谈,这两个概念显然都不可取。

1)单身人士为对象提供全局访问机制。虽然在没有明确定义的初始化顺序的语言中它们可能稍微更线程安全或稍微更可靠,但这种用法仍然是全局变量的道德等价物。它是一个全局变量,装在一些笨拙的语法中(foo :: get_instance()而不是g_foo,比如说),但是它的用途完全相同(在整个程序中可以访问的单个对象)并且具有完全相同的缺点。

2)单身人士阻止了一个类的多个实例化。很少见,IME,这种功能应该融入一个类。这通常是一个更具背景性的事情;许多被认为是独一无二的东西真的只是恰好只有一个。 IMO更合适的解决方案是只创建一个实例 - 直到您意识到需要多个实例。

答案 3 :(得分:26)

模式有一点:不要概括。当它们有用时,以及当它们失败时,它们都有所有的情况。

当你必须测试代码时,单身人士会很讨厌。您通常会遇到类的一个实例,并且可以选择在构造函数中打开一个门,还是在重置状态等方法之间进行选择等。

其他问题在于,单身实际上只不过是伪装的全局变量。当你的程序中有太多的全局共享状态时,事情往往会回归,我们都知道。

可能会使依赖关系跟踪变得更难。当一切都取决于你的单身人士时,更难改变它,分成两个等等。你通常会坚持下去。这也妨碍了灵活性。调查一些依赖注入框架以尝试缓解此问题。

答案 4 :(得分:12)

单身人士基本上会让你在语言中拥有复杂的全局状态,否则很难或不可能拥有复杂的全局变量。

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

一样全局使用

C ++确实有全局变量,但是未定义调用全局类变量的构造函数的顺序。因此,单例允许您推迟创建全局变量,直到第一次需要变量为止。

Python和Ruby等语言很少使用单例,因为您可以在模块中使用全局变量。

那么什么时候使用单身人物好/坏?几乎就是使用全局变量时好/坏。

答案 5 :(得分:6)

  
      
  • 如何正确实施单身人士
  •   

有一个我从未见过的问题,这是我以前的工作遇到的问题。我们有在DLL之间共享的C ++单例,并且确保类的单个实例的常用机制不起作用。问题是每个DLL都有自己的一组静态变量以及EXE。如果你的get_instance函数是内联函数或者是静态库的一部分,那么每个DLL都将使用它自己的“singleton”副本。

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

答案 6 :(得分:6)

Alexandrescu的

现代C ++设计具有线程安全,可继承的通用单例。

对于我的2p值,我认为为你的单身人士确定生命周期非常重要(当使用它们时绝对必要)。我通常不会让静态get()函数实例化任何东西,并将设置和销毁留给主应用程序的某个专用部分。这有助于突出单身人士之间的依赖关系 - 但正如上面所强调的那样,如果可能的话,最好避免它们。

答案 7 :(得分:5)

第一个例子不是线程安全的 - 如果两个线程同时调用getInstance,那个静态就是PITA。某种形式的互斥体会有所帮助。

答案 8 :(得分:4)

正如其他人所指出的那样,单身人士的主要缺点包括无法扩展他们,以及失去实例化多个实例的权力,例如:用于测试目的。

单身人士的一些有用方面:

  1. 懒惰或前期实例化
  2. 方便需要设置和/或状态的对象
  3. 但是,您不必使用单身人士来获得这些好处。您可以编写一个执行该工作的普通对象,然后让人们通过工厂(单独的对象)访问它。如果需要,工厂可以担心只实例化一个,重新使用它等。此外,如果您编程到接口而不是具体类,工厂可以使用策略,即您可以切换进出接口的各种实现。

    最后,工厂适用于依赖注入技术,如Spring等。

答案 9 :(得分:3)

当你初始化和反对时运行大量代码时,单身人士很方便。例如,当您在设置持久性对象时使用iBatis时,它必须读取所有配置,解析映射,确保其全部正确等等。在获取代码之前。

如果你每次都这样做,性能会大打折扣。在单例中使用它,你可以获得一次,然后所有后续调用都不必这样做。

答案 10 :(得分:3)

因为单例只允许创建一个实例,所以它有效地控制了实例复制。例如,您不需要多个查找实例 - 例如莫尔斯查找映射,因此将其包装在单例类中是apt。只是因为你有一个类的实例并不意味着你也受限于对该实例的引用数量。您可以将调用(以避免线程问题)排队到实例并进行必要的更改。是的,单身人士的一般形式是全球公共形式,您当然可以修改设计以创建更多访问受限制的单身人士。我以前没有累过这个,但我确定知道这是可能的。 对于那些评论说单身模式完全是邪恶的人,你应该知道这一点:是的,如果你没有正确地使用它或者在有限的功能和可预测的行为范围内它是邪恶的:不要一般化。

答案 11 :(得分:3)

大多数人在试图让自己对使用全局变量感觉良好时会使用单身人士。有合法的用途,但大多数时候人们使用它们,事实上只有一个实例只是一个微不足道的事实,而不是它可以全球访问的事实。

答案 12 :(得分:3)

单身人士的真正垮台是他们打破了继承权。除非您可以访问引用Singleton的代码,否则无法派生新类来为您提供扩展功能。因此,除了Singleton将使您的代码紧密耦合(可通过策略模式修复...也称为依赖注入)之外,它还将阻止您关闭代码中的部分代码(共享库)。

因此,即使是记录器或线程池的示例也是无效的,应该由Strategies替换。

答案 13 :(得分:2)

但是当我需要像Singleton这样的东西时,我经常最终使用Schwarz Counter来实例化它。

答案 14 :(得分:1)

我使用单身人士作为面试测试。

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

答案 15 :(得分:1)

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

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

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

答案 16 :(得分:0)

反用法:

过度使用单例的一个主要问题是该模式阻止了替代实现的轻松扩展和交换。只要使用单例,类名就会被硬编码。

答案 17 :(得分: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找到此模式。

答案 18 :(得分:0)

迈耶斯单身人士模式大部分时间都运作良好,而且在它做的时候,并不一定要花更多的钱去寻找更好的东西。只要构造函数永远不会抛出,并且单例之间没有依赖关系。

单例是全局可访问对象的实现(从现在开始为GAO),尽管并非所有GAO都是单例。

记录器本身不应该是单例,但记录的方法理想情况下应该是全局可访问的,以便从生成日志消息的位置或记录方式中分离出来。

延迟加载/延迟评估是一个不同的概念,单例通常也会实现。它带来了很多自己的问题,特别是线程安全和问题,如果它失败了,那么当时看起来好主意的事实证明并不是那么好。 (有点像字符串中的COW实现)。

考虑到这一点,GOAs可以这样初始化:

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加载了哪些库 - 记录器可以订阅的消息处理程序,您可以向其发送消息。专门针对信号处理程序而言。

答案 19 :(得分:0)

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

我打算创建一个单例,我将类中的数据库隐藏为私有常量静态变量,并创建利用数据库的类函数,而不会将数据库暴露给用户。

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

答案 20 :(得分:0)

当我有一个封装了大量内存的类时,我觉得它们很有用。例如,在我最近一直在研究的游戏中,我有一个影响力地图类,其中包含一系列非常大的连续内存数组。我希望所有在启动时分配,所有在关机时释放,我绝对只需要它的一个副本。我也必须从很多地方访问它。在这种情况下,我发现单例模式非常有用。

我确信还有其他解决方案,但我发现这个解决方案非常有用且易于实施。

答案 21 :(得分:0)

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

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

答案 22 :(得分:-1)

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

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

答案 23 :(得分:-1)

另一个实现

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

        return *instance_;
    }

private:
    Singleton() {};

    static Singleton *instance_;
};