【设计模式C++】单例模式

静态变量的内存分配和初始化

全局变量、non-local static变量(文件域的静态变量和类的静态成员变量)在main执行之前的静态初始化过程中分配内存并初始化;local static 变量(局部静态变量)则是在第一次使用时分配内存并初始化。这里的变量包含内置数据类型和自定义类型的对象。

静态变量初始化的线程安全性说明

非局部静态变量一般在main执行之前的静态初始化过程中分配内存并初始化,可以认为是线程安全的;

局部静态变量在编译时,编译器的实现一般是在初始化语句之前设置一个局部静态变量的标识来判断是否已经初始化,运行的时候每次进行判断,如果需要初始化则执行初始化操作,否则不执行。这个过程本身不是线程安全的。C++0x之后该实现是线程安全的。

C++11标准针规定了局部静态变量初始化需要保证线程安全,C++03标准并无此说明,具体说明如下:
If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization

单例(Singleton)模式,也叫单子模式,是一种常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。(来自wiki)

参考自:http://www.zkt.name/dan-li-mo-shi-singleton-ji-c-shi-xian/(ZKT编程与生活)

设计模式经典GoF定义的单例模式需要满足以下两个条件:

1)保证一个类只创建一个实例;

2)提供对该实例的全局访问点。

如果系统有类似的实体(有且只有一个,且需要全局访问),那么就可以将其实现为一个单例。实际工作中常见的应用举例:

1)日志类,一个应用往往只对应一个日志实例。

2)配置类,应用的配置集中管理,并提供全局访问。

3)管理器,比如windows系统的任务管理器就是一个例子,总是只有一个管理器的实例。

4)共享资源类,加载资源需要较长时间,使用单例可以避免重复加载资源,并被多个地方共享访问。

Lazy Singleton(懒汉模式)

首先看GoF在描述单例模式时提出的一种实现,教科书式的例子。

//头文件
class Singleton
{
	public:
		static Singleton& Instance()                  //Instance()作为静态成员函数提供里全局访问点
		{
			if(ps == NULL)                        //如果还未实例化,即可实例话,反之提供实例的引用
				ps = new Singleton;
			return *ps;                           //返回指针的话可能会误被 delete,返回引用安全一点
		}

	private:
		Singleton();                                  //这里将构造,析构,拷贝构造,赋值函数设为私有,杜绝了生成新例
		~Singleton();
		Singleton(const Singleton&);
		Singleton& operator=(const Singleton&);

		static Singleton* ps;
};

//源文件
Singleton* Singleton::ps = NULL;

这种方法的好处在于直到 Instance() 被访问,才会生成实例,这种特性被称为延迟初始化(Lazy Initialization),这在一些初始化时消耗较大的情况有很大优势。

Lazy Singleton不是线程安全的,比如现在有线程A和线程B,都通过了 ps == NULL 的判断,那么线程A和B都会创建新实例。单例模式保证生成唯一实例的规则被打破了。

Eager Singleton(饿汉模式)

这种实现在编译器初始化的时候就完成了实例的创建,和上述的Lazy Singleton相反。

//头文件
class Singleton
{
	public:
		static Singleton& Instance()                  //Instance()作为静态成员函数提供里全局访问点
		{
			return instance;
		}

	private:
		Singleton();                                  //这里将构造,析构,拷贝构造,赋值函数设为私有,杜绝了生成新例
		~Singleton();
		Singleton(const Singleton&);
		Singleton& operator=(const Singleton&);

		static Singleton instance;
};

//源文件
Singleton Singleton::instance;

由于实例化是在初始化阶段执行的,所以没有线程安全的问题,但是潜在问题在于no-local static对象(函数外的static对象)在不同编译单元(可理解为cpp文件和其包含的头文件)中的初始化顺序是未定义的。如果在初始化完成之前调用 Instance()方法会返回一个未定义的实例。例如有两个单例 SingletonA 和 SingletonB ,都采用了 Eager Initialization ,那么如果 SingletonA 的初始化需要 SingletonB ,而这两个单例又在不同的编译单元,初始化顺序是不定的,如果
SingletonA 在 SingletonB 之前初始化,就会出错。

Meyers Singleton

为了解决上面的问题,Scott Meyers在《Effective C++》(Item 04)中的提出另一种更优雅的单例模式实现,使用local static对象(函数内的static对象)。当第一次访问 Instance() 方法时才创建实例。

//头文件
class Singleton
{
	public:
		static Singleton& Instance()                  //Instance()作为静态成员函数提供里全局访问点
		{
			static Singleton instance;
			return instance;
		}

	private:
		Singleton();                                  //这里将构造,析构,拷贝构造,赋值函数设为私有,杜绝了生成新例
		~Singleton();
		Singleton(const Singleton&);
		Singleton& operator=(const Singleton&);
};

C++0x之后该实现是线程安全的,有兴趣可以读相关的标准草案(section 6.7),编译器的支持程度不一定,但是G++4.0及以上是支持的。

Double-Checked Locking Pattern(双检测锁模式)

回顾 Lazy Singleton 模式,考虑到线程安全,我们可以通过加锁来保护单例初始化这一过程,双检测锁模式就是在懒汉模式的基础上稍作修改得到:

//头文件
class Singleton
{
	public:
		static Singleton& Instance()              	//Instance()作为静态成员函数提供里全局访问点
		{
			if(ps == NULL)
			{
				Lock();				//上锁
				if(ps == NULL)			//如果还未实例化,即可实例话,反之提供实例的引用
					ps = new Singleton;
				Unlock();			//解锁
			}
			return *ps;                         	//返回指针的话可能会误被 delete,返回引用安全一点
		}	

	private:
		Singleton();                                  	//这里将构造,析构,拷贝构造,赋值函数设为私有,杜绝了生成新例
		~Singleton();
		Singleton(const Singleton&);
		Singleton& operator=(const Singleton&);

		static Singleton* ps;
};

//源文件
Singleton* Singleton::ps = NULL;

以上的上锁和解锁仅用于说明,实际应用中可以使用互斥锁,单一信号量等方法去实现。

这里的两次 ps == NULL,是借鉴了Java的单例模式实现时,使用的所谓的“双检锁模式”(Double-Checked Locking Pattern)。因为进行一次加锁和解锁是需要付出对应的代价的,而进行两次判断,就可以避免多次加锁与解锁操作,同时也保证了线程安全。理论上问题解决了,但是在实践中有很多坑,如指令重排、多核处理器等问题让DCLP实现起来比较复杂比如需要使用内存屏障,详细的分析可以阅读这篇论文

在C++11中有全新的内存模型和原子库,可以很方便的用来实现DCLP。这里不展开。有兴趣可以阅读这篇文章《Double-Checked Locking is Fixed In C++11》

pthread_once

在多线程编程环境下,尽管 pthread_once() 调用会出现在多个线程中,init_routine()函数仅执行一次,pthread_once是很适合用来实现线程安全单例。(pthread_once 在一个进程里只会执行一次,其实现方式使用的就是互斥锁+条件变量的方法)

//头文件
pthread_once_t once = PTHREAD_ONCE_INIT;
class Singleton
{
	public:
		static Singleton& Instance()                  //Instance()作为静态成员函数提供一次实例化以及全局访问点
		{
			pthread_once(&once, &Init);
			return *ps;
		}

		static void Init()
		{
			ps = new Singleton;
		}

	private:
		Singleton();
		~Singleton();
		Singleton(const Singleton&);
		Singleton& operator=(const Singleton&);

		static Singleton* ps;
};
//源文件
Singleton* Singleton::ps = NULL;

总结:

单例模式的实现方法很多,要完成一个完美的实现很难,代码也会很复杂,但是掌握基础的实现还是很必要的。此外还要在实际应用中不断地去优化和探索。除了线程安全,一些场景下还有需要考虑资源释放,生命周期等相关问题,可以参见《Modern C++ Design》中对Singleton的讨论。

时间: 2024-08-07 09:05:43

【设计模式C++】单例模式的相关文章

Java设计模式:单例模式

概念: java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍三种:懒汉式单例.饿汉式单例.登记式单例. 单例模式有以下特点: 1.单例类只能有一个实例. 2.单例类必须自己创建自己的唯一实例. 3.单例类必须给所有其他对象提供这一实例. 单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例.在计算机系统中,线程池.缓存.日志对象.对话框.打印机.显卡的驱动程序对象常被设计成单例.这些应用都或多或少具有资源管理器的功能.每台计算机可以有若干个打印机,但只能

[转]JAVA设计模式之单例模式

原文地址:http://blog.csdn.net/jason0539/article/details/23297037 概念: java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍三种:懒汉式单例.饿汉式单例.登记式单例. 单例模式有以下特点: 1.单例类只能有一个实例. 2.单例类必须自己创建自己的唯一实例. 3.单例类必须给所有其他对象提供这一实例. 单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例.在计算机系统中,线程池.缓存.日志对象.对话

设计模式 之 单例模式

单例模式思路: 私有化构造方法: 防止实例化 私有化克隆方法: 防止克隆 私有化静态属性: 保存对象 公有化静态方法: 获取对象 代码: <?php //设计模式:单例模式 class Singleton { //私有化静态属性:用于保存对象 private static $obj; //私有化构造方法 private function __construct(){} //公有化静态方法:用于实例化对象 public static function getObj() { //判断对象是否存在 i

设计模式实例学习-单例模式(Android中的使用场景)

1.设计模式实例-单例模式 单例模式,故名思议,是指在一个类中通过设置静态使得其仅创造一个唯一的实例.这样设置的目的是满足开发者的希望--这个类只需要被实例化创建一次,同时因为其为静态的缘故,加载的速度也应该快于正常实例化一个类的速度(理论上). 在Android开发中,当我们需要创建一个Fragment的时候常常会用到这样的模式,没有代码的学习是虚无的,接下来亮代码学习: public class SelectFrame extends Fragment { private final sta

(九)JAVA设计模式之单例模式

JAVA设计模式之单例模式 一.单例模式的介绍 Singleton是一种创建型模式,指某个类采用Singleton模式,则在这个类被创建后,只可能产生一个实例供外部访问,并且提供一个全局的访问点.     全局对象和Singleton模式有本质的区别,因为大量使用全局对象会使得程序质量降低,而且有些编程语言根本不支持全局变量.最重要的是传统的全局对象并不能阻止一个类被实例化多次. 二.单例模式的特点 单例类只能有一个实例 单例类必须自己创建自己的唯一实例. 单例类必须给所有其他对象提供这一实例.

C#设计模式(1)——单例模式

一.引言 最近在设计模式的一些内容,主要的参考书籍是<Head First 设计模式>,同时在学习过程中也查看了很多博客园中关于设计模式的一些文章的,在这里记录下我的一些学习笔记,一是为了帮助我更深入地理解设计模式,二同时可以给一些初学设计模式的朋友一些参考.首先我介绍的是设计模式中比较简单的一个模式——单例模式(因为这里只牵涉到一个类) 二.单例模式的介绍 说到单例模式,大家第一反应应该就是——什么是单例模式?,从“单例”字面意思上理解为——一个类只有一个实例,所以单例模式也就是保证一个类只

.NET设计模式之(单例模式)

1.单例模式,一个类只能new一个对象 2.举例,资源管理器,文件管理器,地球等: 3.创建单例: (1)创建一个Earth类 class Earth { public Earth() { } } (2)将构造函数 私有化 class Earth { private Earth() { } } (3)声明一个静态私有的字段,初始化一个实例 class Earth { private static Earth instance=new Earth(); private Earth() { } }

Java 设计模式(3)单例模式

前言 概念: java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍三种:懒汉式单例.饿汉式单例.登记式单例. 单例模式有以下特点: 1.单例类只能有一个实例. 2.单例类必须自己创建自己的唯一实例. 3.单例类必须给所有其他对象提供这一实例. 单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例.在计算机系统中,线程池.缓存.日志对象.对话框.打印机.显卡的驱动程序对象常被设计成单例.这些应用都或多或少具有资源管理器的功能.每台计算机可以有若干个打印机,

设计模式【单例模式】

Java中单例模式是一种常见的设计模式,单例模式分为:饿汉式单例模式.懒汉式单例模式.登记式单例模式.枚举式单例模式.作为对象的常见模式的单例模式,确保某一类只有一个实例,而且自行实例化并向整个系统提供这个实例. 单例模式的特点: 单例类只能有一个实例. 单例类必须自己创建自己的唯一实例. 单例类必须给所有其他对象提供这一实例. 举例说明:在计算机系统中,线程池.缓存.日志对象.对话框.打印机.显卡的驱动程序对象常被设计成单例.单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实

重头开始学23种设计模式:单例模式

最近感觉做程序又开始浑浑噩噩,对设计模式和算法基本了解,但基本不会用.所以打算最近1个月把设计模式和算法重新,温故而知新下. 首先从程序开发经常涉及到的23种设计模式开始,希望这次能更加熟练的运用设计模式来加强自己的开发能力. 首先从单例模式开始: 单例模式在我的理解是对程序对象的缓存,防止不断new,保持对象唯一性,提高程序性能. namespace SinglePattern { class Program { static void Main(string[] args) { for (i