曾经有看过一些文章讲述各种实现方式,似乎并不系统与详实。今天上午同事说在单例中不都有if(xx == null) {}这样的代码吗? 我告诉他NO。 那只是一种实现方式,或许我们常用,但并不代表就只能那样做。接下来我们就看看我们常写的几种单例实现,和boost中的两种单例实现,还有在ACE中单例的实现的不同吧。
singleton 实现一:
class singleton_test
{
private:
singleton_test(){}; //一般来说单例的构造函数都是private的
public:
static singleton_test* get_instance() //一般这个接口都是static的
{
if(!_instance)
{
_instance = new singleton_test();
}
return _instance;
}
void test()
{
cout<<"singleton_test: _instance is " << _instance <<endl;
}
private:
static singleton_test* _instance; //_instance的初始化需要在类外进行,如下面
};
//需要对_instance进行初始化
singleton_test::singleton_test* singleton_test::_instance = NULL;
int main()
{
singleton_test::get_instance()->test();
return 0;
}
评价:上面这种是我在工作中常用的把某个类单例化的做法 。但因为单例化本身是一个独立的东西,参杂在功能类中并不太合适,那我们写个模版类如何?
singleton实例二:
实例二(1)
template <typename T>
class singleton
{
private:
singleton(){}
public:
static T& get_instance()
{
return _obj;
}
private:
static T _obj;
};
template<typename T>
T singleton<T>::_obj;
另一种做法:
实例二(2)
template <typename T>
class singleton_test
{
private:
singleton_test(){}
public:
static T& get_instance()
{
static T _obj;
return _obj;
}
};
这两种做法有什么区别呢?把static T _obj; 放的位置修改了一下而已。
effectie c++的条款4中提到:
(global对象,定义在namespace内的对象,class内的static对象,函数内的static对象,file作用域内的static对象)统称为static对象。
其中函数内的static对象又叫local static object, 其他的叫non-local static object。
non-local static object的初始化顺序是没有定义的,local static object在函数第一次调用时构造初始化。
还有:non-local static object会在main函数之前被初始化。
(从http://www.cnblogs.com/kex1n/archive/2011/04/05/2006194.html看到对此的描述,话说effective c++看完好久,细节的东西仍然是忘记太快了。)
但是!!看似完美的东西,一旦放在多线程环境下,可能就错漏百出了。实例二(2)这里的static T _obj; 在编译后大概形如 if(_obj is not init){ init obj}。可以试想在多线程环境下将会出现的几次init?
(这个我没有考证过,但是我想应该是这么回事)
于是乎,我们想怎么样才能设计出线程安全的singleton版本呢? 加锁? 这是我们最容易想到的一种办法。 好的,ACE就是这么做的,而且使用了所谓的“Double Checked Locking保证线程安全”。
singleton实例三:
template <class TYPE, class ACE_LOCK> TYPE *
ACE_Singleton<TYPE, ACE_LOCK>::instance (void)
{
ACE_TRACE ("ACE_Singleton<TYPE, ACE_LOCK>::instance");
ACE_Singleton<TYPE, ACE_LOCK> *&singleton =
ACE_Singleton<TYPE, ACE_LOCK>::instance_i ();
// Perform the Double-Check pattern...
if (singleton == 0)
{
static ACE_LOCK *lock = 0;
if (ACE_Object_Manager::get_singleton_lock (lock) != 0)
// Failed to acquire the lock!
return 0;
ACE_GUARD_RETURN (ACE_LOCK, ace_mon, *lock, 0);
if (singleton == 0)
{
ACE_NEW_RETURN (singleton, (ACE_Singleton<TYPE, ACE_LOCK>), 0);
// Register for destruction with ACE_Object_Manager.
ACE_Object_Manager::at_exit (singleton);
}
}
return &singleton->instance_;
}
形式回归到我们开头在instance()时才new出实例的设计(顺便想想这种做法和实例二(1)的做法的区别),并且采用了两次check singleton == 0的做法。
为什么两次? 想知道原因我建议看一下这个博文:(http://www.blogjava.net/killvin/archive/2006/09/13/69499.html)。我认为这里讲得很透彻了,确实是很好的一个设计:)
那么boost里面怎么实现的呢,在之前的boost库提供了两个singleton类(其实是个附属品),我今天突然发现在我另一台电脑的1.51.0版本中已经在pool/detail/找不到singleton.hpp文件了,但另一个还躺在那里,它的位置是:boost/serialization/singleton.hpp。其实两个实现原理类似,估计这是其中一个被out掉的原因吧。
可是我仍然把pool/detail/singleton.hpp也贴出来。
singleton实例四:
// T must be: no-throw default constructible and no-throw destructible
template <typename T>
struct singleton_default
{
private:
struct object_creator
{
// This constructor does nothing more than ensure that instance()
// is called before main() begins, thus creating the static
// T object before multithreading race issues can come up.
object_creator() { singleton_default<T>::instance(); }
inline void do_nothing() const { }
};
static object_creator create_object;
singleton_default();
public:
typedef T object_type;
// If, at any point (in user code), singleton_default<T>::instance()
// is called, then the following function is instantiated.
static object_type & instance()
{
// This is the object that we return a reference to.
// It is guaranteed to be created before main() begins because of
// the next line.
static object_type obj;
// The following line does nothing else than force the instantiation
// of singleton_default<T>::create_object, whose constructor is
// called before main() begins.
create_object.do_nothing();
return obj;
}
};
template <typename T>
typename singleton_default<T>::object_creator
singleton_default<T>::create_object;
它是线程安全的,但是有条件的(这个条件一般情况下我们的程序都满足:)。
究其原因大概是由于c++本身不保证non-local static object的初始化顺序,所以这个库的实现者在注释中写了如下话:
//The classes below support usage of singletons, including use in
// program startup/shutdown code, AS LONG AS there is only one thread
// running before main() begins, and only one thread running after main()
// exits.
这里没有使用锁机制,而是充分利用了C++的语言特性较好的解决了多线程情况下使用singleton的问题。
boost的singleton的实现基于以下假设:良好的设计在进入main函数之前应该是单线程的。
我们可以使用全局变量的方式来设计singleton,并且保证在使用该singleton之前其已经被正确的初始化。
另外,这个做法,正好解决了我们实例二中遇到问题,那个问题是:在函数内的static初始化时机为运行时,导致我们的实例2的实现是非线程安全的。
这个boost的实现,我认为它引入的object_creator类,并且在它的构造函数中调用singleton::instance()。把原来的初始化时机从运行时,
提前到了如其它的non-local static object会在main函数之前被初始化。我想,这样算是相对的线程安全咯:)似乎比起ACE的实现来说稍逊一筹?
上面源码中的do_nothing()有什么用?有看过他人的说法,但这确实会让人困惑。 我希望得到你的合理解答。接着看boost最后留存的惟一一个singleton实现。
singleton实例五:
template<class T>
class singleton_wrapper : public T
{
public:
static bool m_is_destroyed;
~singleton_wrapper(){
m_is_destroyed = true;
}
};
template<class T>
bool detail::singleton_wrapper<T>::m_is_destroyed = false;
} // detail
template <class T>
class singleton : public singleton_module
{
private:
BOOST_DLLEXPORT static T & instance;
// include this to provoke instantiation at pre-execution time
static void use(T const &) {}
BOOST_DLLEXPORT static T & get_instance() {
static detail::singleton_wrapper<T> t;
// refer to instance, causing it to be instantiated (and
// initialized at startup on working compilers)
assert(! detail::singleton_wrapper<T>::m_is_destroyed);
use(instance);
return static_cast<T &>(t);
}
public:
BOOST_DLLEXPORT static T & get_mutable_instance(){
assert(! is_locked());
return get_instance();
}
BOOST_DLLEXPORT static const T & get_const_instance(){
return get_instance();
}
BOOST_DLLEXPORT static bool is_destroyed(){
return detail::singleton_wrapper<T>::m_is_destroyed;
}
};
template<class T>
BOOST_DLLEXPORT T & singleton<T>::instance = singleton<T>::get_instance();
看boost两个版本的singleton的实现,我总结如下:
1)对于static T _t;的声明,倾向于将期放到get_instance()函数内,之所以放在函数内而非作为成员变量,前文有述其区别。
2)利用某些技术使放在函数内的原本会在运行期执行的,让它被提前到main()之前,以实现所谓线程安全。
遗留的问题:
1)为什么两个版本的都会类似do_nothing(),use()的东西,到底有何用?
2)第二个版本中,为什么需要使用singleton_wrapper包装一层呢? 后面的assert(! detail::singleton_wrapper<T>::m_is_destroyed); 用作何意?
3)第二个版本中,singleton_module类提供的lock()从来没用过。需要外部调用者自行lock? 感觉这里的lock并非处理线程安全,而是逻辑上的提供一种锁。其存在的意义是?
我的研究结果,我的困惑,求解。当然因为水平有限,可能文章会有各种错误,希望有心人能帮助,指导下偶:)