为什么使用单例模式
使用Head First这本书当中的一段有趣的对话来说明为什么使用单例模式。
开发人员:这有什么用处?
大师:有一些对象其实我们只需要一个,比方说:线程池(threadpool)、缓存(catch)、对话框、处理偏好设置和注册表(registry)的对象、日志对象,充当打印机、显卡等设备的驱动程序的对象。事实上,这类对象只能有一个实例,如果制造出多个实例,就会导致许多问题产生,例如:程序的行为异常、资源使用过量,或者是不一致的结果。
开发人员:好吧!或许的确有一些类应该只存在一个实例,但这需要花整个章节的篇幅来说明吗?难道不能靠程序员之间的约定或是利用全局变量做到?你知道的,例如Java的静态变量就可以做到。
大师:许多时候,的确通过程序员之间的约定就可以办到。但如果有更好的做法,大家都应该乐意接受。别忘了,就跟其它的模式一样,单件模式是经得起时间考验的方法,可以确保只有一个实例会被创建。单件模式也给了我们一个全局的访问点,和全局变量一样方便,有没有全局变量的缺点。
开发人员:什么缺点?
大师:举例来说,如果将对象赋值给一个全局变量,那么你必须在程序一开始就创建好对象(这其实和实现有关。有些JVM的实现是:在用到的时候才创建对象),对吧?万一这个对象非常耗资源,而程序在这次的执行过程中又一直没用到它,不就形成浪费了吗?稍后你会看到,利用单件模式,我们可以在需要时才创建对象。
开发人员:我还是觉得这没有什么困难的。
大师:利用静态类变量、静态方法和适当的访问修饰符(access modifier),你的确可以做到这一点。但是,不管使用哪一种方法,能够了解单件的运作方式仍然是很有趣的事。单件模式听起来简单,要做得对可不简单。不信问问你自己:要如何保证一个对象只能被实例化一次?答案可不是三言两语就能说得完的,是不是?
什么是单例模式?
在Head First这本书当中,是这样来定义单例模式的:
单例模式:确保一个类只有一个实例,并提供全局访问点。
单例模式要点
还是借鉴Head First这本书。
- 单件模式确保程序中一个类最多只有一个实例。
- 单件模式也提供访问这个实例的全局点。
- 在Java中实现单件模式需要私有的构造器,一个静态方法和一个静态变量。
- 确定在性能和资源上的限制,然后小心地选择适当的方案来实现单件,以解决多线程的问题(我们必须认定所有的程序都是多线程的)。
- 如果不是采用第五版的Java 2,双重检查枷锁实现会失效。
- 小心,你如果使用多个类加载器,可能导致单件失效而产生多个实例。
- 如果使用JVM 1.2 或之前的版本,你必须建立单件注册表,以免垃圾收集器将单件回收。
单例模式代码
虽然我们前面都是用的Head First这本书来介绍单例模式相关内容,但是代码我们用的是一个C++类库中的一个部分代码,它是acl(advanced C/C++ library)中的一部分代码(可以说是九牛一毛)。
不过这个方式有一个缺点,就是无论在程序运行过程中是否使用这个单例对象,只要定义了,那么就需要初始化,这样就有可能占用多余的系统资源,这个我们先不考虑。
优点:可以对任何类型的对象进行单例化,因为使用到了模板。
总共用到了3个文件分别是:
noncopyable.hpp
#pragma once
#include "acl_cpp_define.hpp"
namespace acl {
class ACL_CPP_API noncopyable
{
protected:
noncopyable() {}
~noncopyable() {}
private:
noncopyable( const noncopyable& );
const noncopyable& operator=( const noncopyable& );
};
} // namespace acl
acl_cpp_define.hpp
#pragma once
#ifdef ACL_CPP_LIB
# ifndef ACL_CPP_API
# define ACL_CPP_API
# endif
#elif defined(ACL_CPP_DLL) || defined(_WINDLL)
# if defined(ACL_CPP_EXPORTS) || defined(acl_cpp_EXPORTS)
# ifndef ACL_CPP_API
# define ACL_CPP_API __declspec(dllexport)
# endif
# elif !defined(ACL_CPP_API)
# define ACL_CPP_API __declspec(dllimport)
# endif
#elif !defined(ACL_CPP_API)
# define ACL_CPP_API
#endif
/*
#ifndef ACL_CPP_TPL
# ifdef ACL_CPP_DLL
# ifdef ACL_CPP_EXPORTS
# define ACL_CPP_TPL __declspec(dllexport)
# else
# define ACL_CPP_TPL
# endif
# else
# define ACL_CPP_TPL
# endif
#endif
*/
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#ifdef _MSC_VER
# pragma warning(disable:4251)
//# if !defined(VC2003) && !defined(VC6)
//extern "C" { FILE _iob[3] = {__iob_func()[0], __iob_func()[1], __iob_func()[2]}; }
//extern "C" { FILE _iob[3]; }
//# endif
# ifndef HAS_SSIZE_T
# define HAS_SSIZE_T
typedef long ssize_t;
# endif
# if(_MSC_VER >= 1300)
# include <winsock2.h>
# include <mswsock.h>
# else
# include <winsock.h>
# endif
#else
# ifdef HAVE_MEMCACHED
# undef HAVE_MEMCACHED
# endif
#endif
#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ > 4)
#define ACL_CPP_PRINTF(format_idx, arg_idx) \
__attribute__((__format__ (__printf__, (format_idx), (arg_idx))))
#define ACL_CPP_SCANF(format_idx, arg_idx) \
__attribute__((__format__ (__scanf__, (format_idx), (arg_idx))))
#define ACL_CPP_NORETURN __attribute__((__noreturn__))
#define ACL_CPP_UNUSED __attribute__((__unused__))
#else
#define ACL_CPP_PRINTF(format_idx, arg_idx)
#define ACL_CPP_SCANF
#define ACL_CPP_NORETURN
#define ACL_CPP_UNUSED
#endif // __GNUC__
#if __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1)
#define ACL_CPP_DEPRECATED __attribute__((__deprecated__))
#elif defined(_MSC_VER) && (_MSC_VER >= 1300)
#define ACL_CPP_DEPRECATED __declspec(deprecated)
#else
#define ACL_CPP_DEPRECATED
#endif // __GNUC__
#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)
#define ACL_CPP_DEPRECATED_FOR(f) __attribute__((deprecated("Use " #f " instead")))
#elif defined(_MSC_FULL_VER) && (_MSC_FULL_VER > 140050320)
#define ACL_CPP_DEPRECATED_FOR(f) __declspec(deprecated("is deprecated. Use '" #f "' instead"))
#else
#define ACL_CPP_DEPRECATED_FOR(f) ACL_CPP_DEPRECATED
#endif // __GNUC__
#if defined(__GNUC__) && (__GNUC__ > 6 ||(__GNUC__ == 6 && __GNUC_MINOR__ >= 0))
# ifndef ACL_USE_CPP11
# define ACL_USE_CPP11
# endif
#elif defined(_MSC_VER) && (_MSC_VER >= 1900)
# ifndef ACL_USE_CPP11
# define ACL_USE_CPP11
# endif
#endif // __GNUC__
singleton.hpp
#pragma once
#include "acl_cpp_define.hpp"
#include <assert.h>
#include "noncopyable.hpp"
// singleton.hpp
//
// Copyright David Abrahams 2006. Original version
//
// Copyright Robert Ramey 2007. Changes made to permit
// application throughout the serialization library.
//
// Distributed under the Boost
// Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// The intention here is to define a template which will convert
// any class into a singleton with the following features:
//
// a) initialized before first use.
// b) thread-safe for const access to the class
// c) non-locking
//
// In order to do this,
// a) Initialize dynamically when used.
// b) Require that all singletons be initialized before main
// is called or any entry point into the shared library is invoked.
// This guarentees no race condition for initialization.
// In debug mode, we assert that no non-const functions are called
// after main is invoked.
namespace acl {
#if defined(_WIN32) || defined(_WIN64)
# pragma warning(push)
# pragma warning(disable : 4511 4512)
#endif
//////////////////////////////////////////////////////////////////////
// Provides a dynamically-initialized (singleton) instance of T in a
// way that avoids LNK1179 on vc6. See http://tinyurl.com/ljdp8 or
// http://lists.boost.org/Archives/boost/2006/05/105286.php for
// details.
//
// singletons created by this code are guarenteed to be unique
// within the executable or shared library which creates them.
// This is sufficient and in fact ideal for the serialization library.
// The singleton is created when the module is loaded and destroyed
// when the module is unloaded.
// This base class has two functions.
// First it provides a module handle for each singleton indicating
// the executable or shared library in which it was created. This
// turns out to be necessary and sufficient to implement the tables
// used by serialization library.
// Second, it provides a mechanism to detect when a non-const function
// is called after initialization.
// make a singleton to lock/unlock all singletons for alteration.
// The intent is that all singletons created/used by this code
// are to be initialized before main is called. A test program
// can lock all the singletons when main is entereed. This any
// attempt to retieve a mutable instances while locked will
// generate a assertion if compiled for debug.
class singleton_module : public noncopyable
{
public:
static void lock()
{
get_lock() = true;
}
static void unlock()
{
get_lock() = false;
}
static bool is_locked() {
return get_lock();
}
private:
static bool& get_lock()
{
static bool lock_ = false;
return lock_;
}
};
template<class T>
class singleton_wrapper : public T
{
public:
static bool destroyed_;
~singleton_wrapper()
{
destroyed_ = true;
}
};
template<class T>
bool singleton_wrapper< T >::destroyed_ = false;
/**
* 单例模板类,用VC2010或GCC编译时,单例对象在 main 函数之前被执行,
* 所以它是线程安全的;但在 VC2003 编译成 release 版本时且打开了优化
* 开关,则有可能是线程不安全的,此时不能保证单例对象的构造函数在
* main 之前执行.
* 使用举例如下:
* class singleton_test : public acl::singleton<singlegon_test>
* {
* public:
* singleton_test() {}
* ~singleton_test() {}
* singleton_test& init() { return *this; }
* };
* int main()
* {
* singleton_test& test = singleton_test::get_instance();
* test.init();
* ...
* return 0;
* }
*/
template <class T>
class singleton : public singleton_module
{
public:
static T& get_instance()
{
static singleton_wrapper< T > t;
// refer to instance, causing it to be instantiated (and
// initialized at startup on working compilers)
assert(!singleton_wrapper< T >::destroyed_);
use(instance_);
return static_cast<T &>(t);
}
static bool is_destroyed()
{
return singleton_wrapper< T >::destroyed_;
}
private:
static T& instance_;
// include this to provoke instantiation at pre-execution time
static void use(T const &) {}
};
template<class T>
T& singleton< T >::instance_ = singleton< T >::get_instance();
//////////////////////////////////////////////////////////////////////////
/**
* 上面的实现在 VC2003 的 release 编译时如果打开了优化开关,则不能保证单例
* 的构造函数先于 main 执行,如果是在 VC2003 下编译单例程序且在多个线程下
* 都用单例对象时,建议使用如下的单例模板类,示例如下:
* class singleton_test
* {
* public:
* singleton_test() {}
* ~singleton_test() {}
* singleton_test& init() { return *this; }
* };
* int main()
* {
* singleton_test& test = acl::singleton2<singleton_test>::get_instance();
* test.init();
* ...
* return 0;
* }
*
*/
template <typename T>
struct singleton2
{
private:
struct object_creator
{
object_creator() { singleton2<T>::get_instance(); }
inline void do_nothing() const {};
};
static object_creator create_object;
public:
typedef T object_type;
static object_type & get_instance()
{
static object_type obj;
create_object.do_nothing();
return obj;
}
};
template <typename T>
typename singleton2<T>::object_creator singleton2<T>::create_object;
#if defined(_WIN32) || defined(_WIN64)
#pragma warning(pop)
#endif
} // namespace acl
以上就是构成实现单例模式的3个文件及内容。
下面是测试文件
TestSingleton.cpp
// TestSingleton.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <iostream>
#include "singleton.hpp"
class MyTestClass
{
public:
MyTestClass()
{
m_iCount = 0;
std::cout << "MyTestClass()" << std::endl;
}
~MyTestClass()
{
std::cout << "~MyTestClass()" << std::endl;
}
public:
void AddOne()
{
++m_iCount;
}
void SubOne()
{
--m_iCount;
}
void Print() const
{
std::cout << "m_iCount: " << m_iCount << std::endl;
}
private:
int m_iCount;
};
int main()
{
MyTestClass& my_test_class1 = acl::singleton2<MyTestClass>::get_instance();
my_test_class1.Print();
my_test_class1.AddOne();
my_test_class1.Print();
MyTestClass& my_test_class2 = acl::singleton2<MyTestClass>::get_instance();
my_test_class2.Print();
my_test_class2.AddOne();
my_test_class2.Print();
MyTestClass& my_test_class3 = acl::singleton2<MyTestClass>::get_instance();
my_test_class3.Print();
my_test_class3.AddOne();
my_test_class3.Print();
system("pause");
return 0;
}
备注
下面是vs工程的结构
运行效果:
是不是感觉很酷???