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()
它工作正常,因为我们初始化第一次访问B
和C
实例。
其次,销毁阶段:
-
C::~C()
被调用,因为它是3的最后一个构造 -
B::~B()
〜BB::~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
来发信号。
如果(如同A
, B
和C
的例子)我调用一个已经被销毁的对象的实例会发生什么? 那么,在这种情况下,因为我将指针设置为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()
来做这个肮脏的工作。 啧。
哦,没关系。