单例实例声明为GetInstance方法的静态variables
我已经看到实例variables被声明为GetInstance方法中的静态variables的单例模式的实现。 喜欢这个:
SomeBaseClass &SomeClass::GetInstance() { static SomeClass instance; return instance; }
我看到以下积极的方面:
- 代码更简单,因为编译器只有在第一次调用GetInstance时才负责创build这个对象。
- 代码是更安全的,因为没有其他的方式来获取实例的引用,但与GetInstance方法,没有其他方式来更改实例,但在GetInstance方法。
这种方法的消极方面是什么(除了这不是OOP-ish)? 这是线程安全的吗?
在C ++ 11中是线程安全的:
§6.7[stmt.dcl] p4如果控件在variables初始化的同时进入声明,则并发执行应等待初始化完成。
在C ++ 03中:
- 在g ++下它是线程安全的。
但是这是因为g ++显式地添加了代码来保证它。
一个问题是,如果你有两个单身人士,他们在施工和销毁期间尝试使用彼此。
阅读本文: 查找C ++静态初始化顺序问题
这个问题的一个变种是,如果从一个全局variables的析构函数访问单例。 在这种情况下,单例已经被销毁了,但get方法仍然会返回被销毁对象的引用。
有办法解决这个问题,但是他们很混乱,不值得去做。 只是不要从全局variables的析构函数中访问单例。
更安全的定义,但丑陋的:
我相信你可以添加一些适当的macros来整理这个
SomeBaseClass &SomeClass::GetInstance() { #ifdef _WIN32 Start Critical Section Here #elif defined(__GNUC__) && (__GNUC__ > 3) // You are OK #else #error Add Critical Section for your platform #endif static SomeClass instance; #ifdef _WIN32 END Critical Section Here #endif return instance; }
如图所示,它不是线程安全的。 C ++语言在线程上保持沉默,所以你没有语言的内在保证。 您将不得不使用平台同步原语,例如Win32 :: EnterCriticalSection()来保护访问。
编译器会在第一次调用时插入一些(非线程安全的)代码来初始化静态instance
,这很可能是在函数体开始执行之前(因此在调用任何同步之前) 。)
使用指向SomeClass
的全局/静态成员指针,然后在一个同步块内初始化将会certificate执行起来不太麻烦。
#include <boost/shared_ptr.hpp> namespace { //Could be implemented as private member of SomeClass instead.. boost::shared_ptr<SomeClass> g_instance; } SomeBaseClass &SomeClass::GetInstance() { //Synchronize me eg ::EnterCriticalSection() if(g_instance == NULL) g_instance = boost::shared_ptr<SomeClass>(new SomeClass()); //Unsynchronize me eg :::LeaveCriticalSection(); return *g_instance; }
我没有编译这个,所以只是为了说明的目的。 它也依赖于boost库来获得与你最初的例子相同的生命周期(或那里)。 你也可以使用std :: tr1(C ++ 0x)。
根据规格,这也应该在VC ++中工作。 有人知道吗?
只需添加关键字volatile。 如果msdn上的doc是正确的话,visual c ++编译器应该生成互斥量。
SomeBaseClass &SomeClass::GetInstance() { static volatile SomeClass instance; return instance; }
它分享了Singleton实现的所有常见错误,即:
- 这是不可测的
- 它不是线程安全的(这是很简单的,看看你是否想象两个线程同时进入函数)
- 这是一个内存泄漏
我build议在任何生产代码中不要使用Singleton。