C++中的单例模式

sky*_*oor 46 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
};
Run Code Online (Sandbox Code Playgroud)

一个是指针,就像这样

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;
    }
};
Run Code Online (Sandbox Code Playgroud)

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

Mat*_* M. 60

你应该阅读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;
Run Code Online (Sandbox Code Playgroud)

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

一,施工阶段:

  • A globalA;被执行,A::A()被称为
  • A::A() 电话 B::B()
  • A::A() 电话 C::C()

它运行正常,因为我们在第一次访问时初始化BC实例.

二,破坏阶段:

  • C::~C() 被称为因为它是3的最后一个构造
  • B::~B()被称为... oups,它试图访问C的实例!

因此,我们在破坏时有不明确的行为,哼......

3.新战略

这里的想法很简单.全局内置函数在其他全局变量之前初始化,因此您的指针将被设置为0在您编写的任何代码被调用之前,它确保测试:

S& S::Instance() { if (MInstance == 0) MInstance = new S(); return *MInstance; }
Run Code Online (Sandbox Code Playgroud)

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

不过已经说过,这里有一个内存泄漏,最糟糕的是一个永远不会被调用的析构函数.解决方案存在,并且是标准化的.这是对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!!!
Run Code Online (Sandbox Code Playgroud)

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

第二,它是如何工作的?好吧,与前一个用例完全一样:在初始化时,它会构建一个指向函数的指针堆栈,以便在调用时将堆栈清空一次.因此,实际上,函数以Last-In First-Out方式调用.

那么这里发生了什么?

  • 第一次访问时构造(初始化很好),我注册CleanUp退出时间的方法

  • 退出时间:CleanUp调用该方法.它会破坏对象(因此我们可以在析构函数中有效地工作)并重置指针以0发出信号.

如果(例如with A,BC)我调用已经被破坏的对象的实例会发生什么?好吧,在这种情况下,因为我将指针设置为0我将重建一个临时单例并且循环重新开始.它不会活很长时间,因为我正在我的堆栈.

亚历山大雷斯库(Alexandrescu)称它为Phoenix Singleton灰烬,如果它被摧毁后需要它从灰烬中复活.

另一种方法是使用静态标志并destroyed在清理期间将其设置为并让用户知道它没有获得单例的实例,例如通过返回空指针.返回指针(或引用)的唯一问题是你最好希望没有人愚蠢到可以调用delete它:/

4.幺半群模式

既然我们在谈论Singleton我认为是时候介绍Monoid模式了.从本质上讲,它可以被视为Flyweight模式的退化情况,或者使用Proxy结束Singleton.

Monoid模式很简单:类共享公共状态的所有实例.

我将借此机会揭露非凤凰实施:)

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;
}
Run Code Online (Sandbox Code Playgroud)

有什么好处?它隐藏了这样一个事实:国家是共享的,它隐藏着Singleton.

  • 如果您需要有两种不同的状态,那么您可以设法在不更改使用它的每一行代码的情况下执行此操作(例如,Singleton通过调用替换Factory)
  • Nodoby将打电话delete给你的单身人士的实例,所以你真正管理状态并防止意外......无论如何你无法对恶意用户做多少事情!
  • 你可以控制对单例的访问,所以如果它被销毁后被调用,你可以正确处理它(什么也不做,记录等等......)

5.最后一句话

尽管看起来很完整,但我想指出我很高兴地浏览了任何多线程问题...阅读Alexandrescu的Modern C++以了解更多信息!

  • "我返回指针(或引用)的唯一问题是,你最好希望没有人愚蠢到可以调用删除:/"你已经使你的析构函数变得私有,所以他们必须走出去他们这样做的方式. (4认同)