C ++中的单例模式

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

我看到了两个关于单身课程中的静态成员的例子。

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

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 …谈论debugging的噩梦,我会记得一个而:/

1.单身人士的生活

单身人士的主要问题是终生pipe理。

如果你曾经尝试过使用这个物体,那么你需要活着和踢。 因此,这个问题来自初始化和破坏,这是C ++与全局variables中的一个常见问题。

初始化通常是最容易纠正的。 正如两种方法所表明的那样,初次使用就足够简单了。

破坏有点微妙。 全局variables按照与之相反的顺序销毁。 所以在本地静态的情况下,你并没有真正控制的东西….

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; 

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

一,build设阶段:

  • A globalA; 被执行, A::A()被调用
  • A::A()调用B::B()
  • A::A()调用C::C()

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

其次,销毁阶段:

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

因此,我们在破坏,嗡嗡声中有不确定的行为

3.新的战略

这个想法很简单。 全局内置函数在其他全局variables之前被初始化,所以在你写的代码被调用之前,你的指针将被设置为0 ,这样可以确保testing:

 S& S::Instance() { if (MInstance == 0) MInstance = new S(); return *MInstance; } 

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

然而,据说,这里有一个内存泄漏,最糟糕的是从未被调用过的析构函数。 解决scheme存在,并且是标准化的。 这是对atexit函数的调用。

atexit函数允许您指定在程序closures期间执行的操作。 有了这个,我们可以写一个singleton:

 // 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)); 即它接受一个函数的指针,该函数不需要任何参数,也不返回任何内容。

其次,它是如何工作的? 那么,就像以前的用例一样:在初始化时,它build立一个指向函数的指针堆栈,并在销毁时一次清空堆栈。 所以,实际上,函数以后进先出的方式被调用。

那么这里会发生什么?

  • 首次访问构build(初始化正常),我注册CleanUp方法的退出时间

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

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

亚历山德雷斯库把它称为“ Phoenix Singleton ”( Phoenix Singleton因为它在被摧毁之后,如果需要的话,它会从灰烬中复活。

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

4. Monoid模式

既然我们在谈论Singleton我认为是时候引入Monoid模式了。 实质上,它可以被看作是Flyweight模式的退化情况,或者是使用Proxy over 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; } 

有什么好处? 它隐藏了国家共享的事实,它隐藏了Singleton

  • 如果你需要有两种截然不同的状态,那么你可能会设法做到这一点,而不必改变每一行代码(例如通过调用Factory来替代Singleton
  • Nodoby会打电话给你的单身人士的实例,所以你真的pipe理状态,并防止意外…你不能做太多反对恶意用户!
  • 你控制对单身人士的访问,所以如果它在被销毁后被调用,你可以正确处理它(不做任何事情,login等等)

5.最后一句话

尽pipe看起来完整无缺,但我想指出的是,我已经快乐地剔除了任何multithreading问题…阅读Alexandrescu的Modern C ++以了解更多信息!

两者都不是比另一个更正确。 总的来说,我倾向于尽量避免使用单身人士,但是当我一直面临着想要走的路时,我已经使用了这两种方法,并且他们工作的很好。

指针选项的一个问题是它会泄漏内存。 另一方面,你的第一个例子最终可能在你完成之前就被破坏了,所以无论你是否select找一个更合适的东西来做这件事,创造并在适当的时间销毁它。

不同的是第二个泄漏内存(单身本身),而第一个没有。 静态对象在第一次调用关联的方法时被初始化,并且(只要程序完全退出),它们在程序退出之前被销毁。 带指针的版本会在程序退出时分配指针,像Valgrind这样的内存检查器会报错。

另外,什么阻止某人做delete GlobalClass::instance();

由于以上两个原因,使用静态的版本是更常用的方法,并且是原始devise模式书中规定的。

使用第二种方法 – 如果你不想使用atexit来释放你的对象,那么你总是可以使用pipe理器对象(例如auto_ptr或自己写的东西)。 在完成对象之前,这可能会导致释放,就像使用第一个方法一样。

不同的是,如果你使用静态对象,你基本上无法检查它是否已经被释放。

如果你使用指针,你可以添加额外的静态布尔值来指示单例是否已经被破坏(如在Monoid中)。 然后你的代码可以随时检查单例是否已经被破坏,尽pipe你打算做的事情可能会失败,但至less你不会得到神秘的“segmentaion fault”或“access violation”,程序将避免exception终止。

我同意比利。 在第二种方法中,我们使用new从堆中dynamic分配内存。 这个内存保持不变,永远不会被释放,除非有删除的呼叫。 因此,全局指针方法会造成内存泄漏。

 class singleton { private: static singleton* single; singleton() { } singleton(const singleton& obj) { } public: static singleton* getInstance(); ~singleton() { if(single != NULL) { single = NULL; } } }; singleton* singleton :: single=NULL; singleton* singleton :: getInstance() { if(single == NULL) { single = new singleton; } return single; } int main() { singleton *ptrobj = singleton::getInstance(); delete ptrobj; singleton::getInstance(); delete singleton::getInstance(); return 0; } 

你的第一个例子对于一个单身人士来说更为典型。 你的第二个例子不同之处在于它是按需创build的。

不过,我会尽量避免使用单例,因为它们不过是全局variables。

更好的方法是创build一个单例类。 这也避免了GetInstance()函数中的实例可用性检查。 这可以使用函数指针来实现。

 class TSingleton; typedef TSingleton* (*FuncPtr) (void); class TSingleton { TSingleton(); //prevent public object creation TSingleton (const TSingleton& pObject); // prevent copying object static TSingleton* vObject; // single object of a class static TSingleton* CreateInstance (void); static TSingleton* Instance (void); public: static FuncPtr GetInstance; }; FuncPtr TSingleton::GetInstance = CreateInstance; TSingleton* TSingleton::vObject; TSingleton::TSingleton() { } TSingleton::TSingleton(const TSingleton& pObject) { } TSingleton* TSingleton::CreateInstance(void) { if(vObject == NULL){ // Introduce here some code for taking lock for thread safe creation //... //... //... if(vObject == NULL){ vObject = new TSingleton(); GetInstance = Instance; } } return vObject; } TSingleton* TSingleton::Instance(void) { return vObject; } void main() { TSingleton::GetInstance(); // this will call TSingleton::Createinstance() TSingleton::GetInstance(); // this will call TSingleton::Instance() // all further calls to TSingleton::GetInstance will call TSingleton::Instance() which simply returns already created object. } 

为了回应“内存泄露”的投诉,有一个简单的解决方法:

 // dtor ~GlobalClass() { if (this == s_instance) s_instance = NULL; } 

换句话说,给这个类一个析构函数,当程序终止时,单身对象被破坏时,它会去掉初始化隐藏的指针variables。

一旦你这样做了,这两种forms实际上是相同的。 唯一显着的区别是返回一个隐藏对象的引用,而另一个返回一个指向它的指针。

更新

正如@BillyONeal指出的那样,这是行不通的,因为指向的对象永远不会被删除 。 哎哟。

我讨厌甚至想想,但是你可以用atexit()来做这个肮脏的工作。 啧。

哦,没关系。