coolshell上有篇文章将单例,讲的已经很好了,最近看了boost的实现,感觉更有一些体会。
原文使用的是java来进行讲解,涉及到了并发的场景。但我这里要解释的,是c++中的单例模式,当然,是通过学习boost的实现。 (boost 1.57.0)
boost中有一些分散的单例实现,能够独立摘出来用的主要有以下四个:
- boost/container/detail/singleton.hpp
- boost/serialization/singleton.hpp
- boost/thread/detail/singleton.hpp
- boost/pool/singleton_pool.hpp
尤其以前两个为主。
1. boost/container/detail/singleton.hpp
代码如下:
// T must be: no-throw default constructible and no-throw destructible
template
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::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::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::create_object, whose constructor is
// called before main() begins.
create_object.do_nothing();
return obj;
}
};
template
typename singleton_default::object_creator
singleton_default::create_object;
其实注释已经说得很清楚了,这个实现使用了一个struct object_creator来作为类的static成员变量,单例的实体是obj,并不暴露出来,只是作为类的成员函数的static变量(local static对象)。通过create_object在main之前被执行构造来保证单例是在main之前被构造好。
这个单例是利用main调用之前,程序只有一个线程,来保证单例在多线程下的唯一性。
其中最迷惑人的也就就是create_object.do_nothing(); 这么用的具体原因,可以参考C++标准中的3.6.2 Initialization of non-local variables:
- It is implementation-defined whether the dynamic initialization of a non-local variable with static storage
duration is done before the first statement of main...
2. boost/serialization/singleton.hpp
代码如下:
class singleton_module :
public boost::noncopyable
{
private:
static bool & get_lock(){
static bool lock = false;
return lock;
}
public:
// static const void * get_module_handle(){
// return static_cast(get_module_handle);
// }
static void lock(){
get_lock() = true;
}
static void unlock(){
get_lock() = false;
}
static bool is_locked() {
return get_lock();
}
};
namespace detail {
template
class singleton_wrapper : public T
{
public:
static bool m_is_destroyed;
~singleton_wrapper(){
m_is_destroyed = true;
}
};
template
bool detail::singleton_wrapper< T >::m_is_destroyed = false;
} // detail
template
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)
BOOST_ASSERT(! detail::singleton_wrapper< T >::m_is_destroyed);
use(instance);
return static_cast(t);
}
public:
BOOST_DLLEXPORT static T & get_mutable_instance(){
BOOST_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
BOOST_DLLEXPORT T & singleton< T >::instance = singleton< T >::get_instance();
这个实现和detail中的类似,同样通过static变量来完成单例的第一次调用,来保证在main之前构造好,同样使用了一个use来显示的调用static成员变量,触发3.6.2 Initialization of non-local variables。唯一不同的是单例的接口支持了mutable,但是看不出来实际中有什么作用,注释中说了,在debug中可以通过设置lock来测试修改全体单例的地方,可能这个就是用处吧。
3. boost/thread/detail/singleton.hpp
代码:
// class singleton has the same goal as all singletons: create one instance of
// a class on demand, then dish it out as requested.
template
class singleton : private T
{
private:
singleton();
~singleton();
public:
static T &instance();
};
template
inline singleton::singleton()
{
/* no-op */
}
template
inline singleton::~singleton()
{
/* no-op */
}
template
/*static*/ T &singleton::instance()
{
// function-local static to force this to work correctly at static
// initialization time.
static singleton s_oT;
return(s_oT);
}
这个thread中的实现就清晰的多,只不过单例是在调用时第一次访问,而不是在main之前,这对于多线程程序来说,要格外注意的。
不过在c++11后,要求编译器保证内部静态变量的线程安全性,因此在支持c++11的编译器中这种方法可以产生线程安全的单例模式,然而在不支持c++11的编译器中,这种方法无法得到保证。
4. boost/pool/singleton_pool.hpp
代码:
template
class singleton_pool
{
public:
typedef Tag tag; /*!< The Tag template parameter uniquely
identifies this pool and allows
different unbounded sets of singleton pools to exist.
For example, the pool allocators use two tag classes to ensure that the
two different allocator types never share the same underlying singleton pool.
Tag is never actually used by singleton_pool.
*/
typedef Mutex mutex; //!< The type of mutex used to synchonise access to this pool (default details::pool::default_mutex).
typedef UserAllocator user_allocator; //!< The user-allocator used by this pool, default = default_user_allocator_new_delete.
typedef typename pool::size_type size_type; //!< size_type of user allocator.
typedef typename pool::difference_type difference_type; //!< difference_type of user allocator.
BOOST_STATIC_CONSTANT(unsigned, requested_size = RequestedSize); //!< The size of each chunk allocated by this pool.
BOOST_STATIC_CONSTANT(unsigned, next_size = NextSize); //!< The number of chunks to allocate on the first allocation.
private:
singleton_pool();
#ifndef BOOST_DOXYGEN
struct pool_type: public Mutex, public pool
{
pool_type() : pool(RequestedSize, NextSize, MaxSize) {}
}; // struct pool_type: Mutex
#else
//
// This is invoked when we build with Doxygen only:
//
public:
static pool p; //!< For exposition only!
#endif
public:
static void * malloc BOOST_PREVENT_MACRO_SUBSTITUTION()
{ //! Equivalent to SingletonPool::p.malloc(); synchronized.
pool_type & p = get_pool();
details::pool::guard g(p);
return (p.malloc)();
}
static void * ordered_malloc()
{ //! Equivalent to SingletonPool::p.ordered_malloc(); synchronized.
pool_type & p = get_pool();
details::pool::guard g(p);
return p.ordered_malloc();
}
static void * ordered_malloc(const size_type n)
{ //! Equivalent to SingletonPool::p.ordered_malloc(n); synchronized.
pool_type & p = get_pool();
details::pool::guard g(p);
return p.ordered_malloc(n);
}
static bool is_from(void * const ptr)
{ //! Equivalent to SingletonPool::p.is_from(chunk); synchronized.
//! \returns true if chunk is from SingletonPool::is_from(chunk)
pool_type & p = get_pool();
details::pool::guard g(p);
return p.is_from(ptr);
}
static void free BOOST_PREVENT_MACRO_SUBSTITUTION(void * const ptr)
{ //! Equivalent to SingletonPool::p.free(chunk); synchronized.
pool_type & p = get_pool();
details::pool::guard g(p);
(p.free)(ptr);
}
static void ordered_free(void * const ptr)
{ //! Equivalent to SingletonPool::p.ordered_free(chunk); synchronized.
pool_type & p = get_pool();
details::pool::guard g(p);
p.ordered_free(ptr);
}
static void free BOOST_PREVENT_MACRO_SUBSTITUTION(void * const ptr, const size_type n)
{ //! Equivalent to SingletonPool::p.free(chunk, n); synchronized.
pool_type & p = get_pool();
details::pool::guard g(p);
(p.free)(ptr, n);
}
static void ordered_free(void * const ptr, const size_type n)
{ //! Equivalent to SingletonPool::p.ordered_free(chunk, n); synchronized.
pool_type & p = get_pool();
details::pool::guard g(p);
p.ordered_free(ptr, n);
}
static bool release_memory()
{ //! Equivalent to SingletonPool::p.release_memory(); synchronized.
pool_type & p = get_pool();
details::pool::guard g(p);
return p.release_memory();
}
static bool purge_memory()
{ //! Equivalent to SingletonPool::p.purge_memory(); synchronized.
pool_type & p = get_pool();
details::pool::guard g(p);
return p.purge_memory();
}
private:
typedef boost::aligned_storage::value> storage_type;
static storage_type storage;
static pool_type& get_pool()
{
static bool f = false;
if(!f)
{
// This code *must* be called before main() starts,
// and when only one thread is executing.
f = true;
new (&storage) pool_type;
}
// The following line does nothing else than force the instantiation
// of singleton::create_object, whose constructor is
// called before main() begins.
create_object.do_nothing();
return *static_cast(static_cast(&storage));
}
struct object_creator
{
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.
singleton_pool::get_pool();
}
inline void do_nothing() const
{
}
};
static object_creator create_object;
}; // struct singleton_pool
template
typename singleton_pool::storage_type singleton_pool::storage;
template
typename singleton_pool::object_creator singleton_pool::create_object;
这个是在pool中的单例实现,所有的接口都和pool类似,使用也类似,单例的实现类似于detail的实现,包括do_nothing()的使用,显示线程安全和在main之前构造好的,细节参考detail的实现。
看了上面4个单例的实现,基本上1最典型,保证了main之前的调用。但是boost为什么没有一个通用的单例类呢?是因为Are Singletons really that bad?
可以在stackoverflow搜搜这个讨论,可能这也是boost没有一个通用的单例类的原因,已提供的单例类,都是在某个具体的模块中。学习不代表要使用。