c++ - C++中的单例模式

标签 c++ singleton

我有一个关于单例模式的问题。

我在单例类中看到了两个关于静态成员的案例。

首先它是一个对象,像这样

class CMySingleton
{
public:
  static CMySingleton& Instance()
  {
    static CMySingleton singleton;
    return singleton;
  }

// Other non-static member functions
private:
  CMySingleton() {}                                  // Private constructor
  ~CMySingleton() {}
  CMySingleton(const CMySingleton&);                 // Prevent copy-construction
  CMySingleton& operator=(const CMySingleton&);      // Prevent assignment
};

一个是指针,像这样
class GlobalClass
{
    int m_value;
    static GlobalClass *s_instance;
    GlobalClass(int v = 0)
    {
        m_value = v;
    }
  public:
    int get_value()
    {
        return m_value;
    }
    void set_value(int v)
    {
        m_value = v;
    }
    static GlobalClass *instance()
    {
        if (!s_instance)
          s_instance = new GlobalClass;
        return s_instance;
    }
};

这两种情况有什么区别?哪一个是正确的?

最佳答案

您可能应该阅读 Alexandrescu 的书。

关于本地静态,我有一段时间没有使用Visual Studio,但是在用Visual Studio 2003编译时,每个DLL都分配了一个本地静态......说一下调试的噩梦,我会记住一个尽管 :/

1. 单例的生命周期

关于单例的主要问题是生命周期管理。

如果您曾经尝试使用该对象,则需要保持活力。因此,问题来自初始化和销毁​​,这是 C++ 中带有全局变量的常见问题。

初始化通常是最容易纠正的事情。正如这两种方法所建议的那样,在第一次使用时进行初始化非常简单。

破坏要微妙一些。全局变量的销毁顺序与它们创建的顺序相反。所以在本地静态情况下,你实际上并没有控制事情......

2.本地静态

struct A
{
  A() { B::Instance(); C::Instance().call(); }
};

struct B
{
  ~B() { C::Instance().call(); }
  static B& Instance() { static B MI; return MI; }
};

struct C
{
  static C& Instance() { static C MI; return MI; }
  void call() {}
};

A globalA;

这里有什么问题?让我们检查构造函数和析构函数的调用顺序。

一、施工阶段:
  • A globalA;被执行,A::A()被称为
  • A::A()电话B::B()
  • A::A()电话C::C()

  • 它工作正常,因为我们初始化了 BC第一次访问的实例。

    二、销毁阶段:
  • C::~C()被调用是因为它是最后一次构造的 3
  • B::~B()被称为... 哎呀,它试图访问 C的实例!

  • 因此,我们在破坏时有未定义的行为,嗯......

    3.新战略

    这里的想法很简单。全局内置函数在其他全局变量之前初始化,因此您的指针将设置为 0在您编写的任何代码被调用之前,它确保测试:
    S& S::Instance() { if (MInstance == 0) MInstance = new S(); return *MInstance; }
    

    实际上会检查实例是否正确。

    然而,有人说过,这里存在内存泄漏,最糟糕的是一个永远不会被调用的析构函数。解决方案存在,并且是标准化的。打给 atexit功能。
    atexit函数让您指定要在程序关闭期间执行的操作。有了这个,我们就可以写一个单例了:
    // in s.hpp
    class S
    {
    public:
      static S& Instance(); // already defined
    
    private:
      static void CleanUp();
    
      S(); // later, because that's where the work takes place
      ~S() { /* anything ? */ }
    
      // not copyable
      S(S const&);
      S& operator=(S const&);
    
      static S* MInstance;
    };
    
    // in s.cpp
    S* S::MInstance = 0;
    
    S::S() { atexit(&CleanUp); }
    
    S::CleanUp() { delete MInstance; MInstance = 0; } // Note the = 0 bit!!!
    

    首先,让我们详细了解atexit .签名是int atexit(void (*function)(void)); ,即它接受一个指向一个函数的指针,该函数不接受任何参数,也不返回任何内容。

    其次,它是如何工作的?好吧,就像前面的用例一样:在初始化时,它构建了一个指向函数调用的指针堆栈,并在销毁时一次清空堆栈中的一项。因此,实际上,函数是以后进先出的方式调用的。

    那么这里会发生什么?
  • 第一次访问的构建(初始化很好),我注册了 CleanUp退出时间的方法
  • 退出时间:CleanUp方法被调用。它销毁对象(因此我们可以有效地在析构函数中工作)并将指针重置为 0发出信号。

  • 如果(就像在 ABC 的例子中)我调用一个已经被销毁的对象的实例会发生什么?好吧,在这种情况下,因为我将指针重新设置为 0我将重建一个临时单例,然后循环重新开始。不过因为我正在清理我的堆栈,它不会存活很长时间。

    Alexandrescu 称它为 Phoenix Singleton当它被摧毁后,如果需要它,它会从 Ember 中复活。

    另一种选择是有一个静态标志并将其设置为 destroyed在清理过程中,让用户知道它没有得到单例的实例,例如通过返回一个空指针。我在返回指针(或引用)时遇到的唯一问题是,你最好希望没有人傻到会调用 delete。在上面 :/

    4.幺半群模式

    既然我们在谈论 Singleton我想是时候介绍 Monoid图案。本质上可以看作是Flyweight的退化案例。模式,或使用 ProxySingleton .
    Monoid模式很简单:类的所有实例共享一个公共(public)状态。

    我将借此机会公开非凤凰实现:)
    class Monoid
    {
    public:
      void foo() { if (State* i = Instance()) i->foo(); }
      void bar() { if (State* i = Instance()) i->bar(); }
    
    private:
      struct State {};
    
      static State* Instance();
      static void CleanUp();
    
      static bool MDestroyed;
      static State* MInstance;
    };
    
    // .cpp
    bool Monoid::MDestroyed = false;
    State* Monoid::MInstance = 0;
    
    State* Monoid::Instance()
    {
      if (!MDestroyed && !MInstance)
      {
        MInstance = new State();
        atexit(&CleanUp);
      }
      return MInstance;
    }
    
    void Monoid::CleanUp()
    {
      delete MInstance;
      MInstance = 0;
      MDestroyed = true;
    }
    

    有什么好处?它隐藏了状态是共享的事实,它隐藏了 Singleton .
  • 如果您需要有 2 个不同的状态,您可能会设法做到这一点,而无需更改使用它的每一行代码(例如,将 Singleton 替换为对 Factory 的调用)
  • Nodoby 将调用 delete在你的单例的实例上,所以你真正管理状态并防止事故......无论如何你不能对恶意用户做太多!
  • 您控制对单例的访问,因此如果它在被销毁后被调用,您可以正确处理它(什么都不做,记录等...)

  • 5. 最后的话

    尽管这看起来很完整,但我想指出的是,我很高兴地浏览了所有多线程问题……阅读 Alexandrescu 的现代 C++ 以了解更多信息!

    关于c++ - C++中的单例模式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2496918/

    相关文章:

    c# - 如何处理 OO 应用程序中的横切关注点?使用单例?依赖注入(inject)?什么?

    java - java中SomeObject.class的含义是什么?

    c++ - 将函数指针转换为另一个具有更多参数的函数指针

    c++ - 如何在 C++ 函数中输入变量名并使用该变量?

    Java 最佳实践 : Class with only static methods

    c# - 我是否需要在 ASP.NET 中配置 Web 服务引用?我可以使用单例吗?

    ios - 我如何将这个 Swift 3 Singleton 用作我的 IOS 项目的全局计时器?

    c++ - 如何将字符串解析为已知结构 C++ 中的数字

    c++ - 如何从一个函数返回多个类型?

    c++ - 如何重构容器以直接使用谓词方法?