C++ 单例模式的几种实现研究
都是从网上学得,整理下自己的理解。
单例模式有两种实现模式:
1)懒汉模式: 就是说当你第一次使用时才创建一个唯一的实例对象,从而实现延迟加载的效果。
2)饿汉模式: 就是说不管你将来用不用,程序启动时就创建一个唯一的实例对象。
所以,从实现手法上看, 懒汉模式是在第一次使用单例对象时才完成初始化工作。因为此时可能存在多线程竞态环境,如不加锁限制会导致重复构造或构造不完全问题。
饿汉模式则是利用外部变量,在进入程序入口函数之前就完成单例对象的初始化工作,此时是单线程所以不会存在多线程的竞态环境,故而无需加锁。
以下是典型的几种实现
一、 懒汉模式,标准的 ”双检锁“ + ”自动回收“ 实现
class Singleton
{
public:
static Singleton* GetInstance()
{
if (m_pInstance == NULL )
{
Lock(); // 加锁
if (m_pInstance == NULL )
{
m_pInstance = new Singleton ();
}
UnLock(); // 解锁
}
return m_pInstance;
} // 实现一个内嵌垃圾回收类
class CGarbo
{
public:
~CGarbo()
{
if(Singleton::m_pInstance)
delete Singleton::m_pInstance;
}
}; static CGarbo Garbo; // 定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数从而释放单例对象 private:
Singleton(){};
Singleton(Singleton const&);
Singleton& operator=(Singleton const&); static Singleton* m_pInstance;
}; Singleton* Singleton::m_pInstance = NULL;
Singleton::CGarbo Garbo;
二、静态局部变量的懒汉模式 ,而不是new在堆上创建对象,避免自己回收资源。
这里仍然要注意的是局部变量初始化的线程安全性问题,在C++0X以后,要求编译器保证静态变量初始化的线程安全性,可以不加锁。但C++ 0X以前,仍需要加锁。
class Singleton
{
public:
static Singleton* GetInstance()
{
Lock(); // not needed after C++0x
static Singleton instance;
UnLock(); // not needed after C++0x return &instance;
} private:
Singleton() {};
Singleton(const Singleton &);
Singleton & operator = (const Singleton &);
};
在懒汉模式里,如果大量并发线程获取单例对象,在进行频繁加锁解锁操作时,必然导致效率低下。
三、饿汉模式,基础版本
因为程序一开始就完成了单例对象的初始化,所以后续不再需要考虑多线程安全性问题,就可以避免懒汉模式里频繁加锁解锁带来的开销。
class Singleton
{
public: static Singleton* GetInstance()
{
return &m_instance;
} private:
Singleton(){};
Singleton(Singleton const&);
Singleton& operator=(Singleton const&); static Singleton m_instance;
}; Singleton Singleton::m_instance; // 在程序入口之前就完成单例对象的初始化
虽然这种实现在一定程度下能良好工作,但是在某些情况下会带来问题 --- 就是在C++中 ”非局部静态对象“ 的 ”初始化“ 顺序 的 ”不确定性“, 参见Effective c++ 条款47。
考虑: 如果有两个这样的单例类,将分别生成单例对象A, 单例对象B. 它们分别定义在不同的编译单元(cpp中), 而A的初始化依赖于B 【 即A的构造函数中要调用B::GetInstance() ,而此时B::m_instance 可能还未初始化,显然调用结果就是非法的 】, 所以说只有B在A之前完成初始化程序才能正确运行,而这种跨编译单元的初始化顺序编译器是无法保证的。
四、饿汉模式,增强版本(boost实现)
在前面的方案中:饿汉模式中,使用到了类静态成员变量,但是遇到了初始化顺序的问题; 懒汉模式中,使用到了静态局部变量,但是存在着线程安全等问题。
boost 的实现方式是:单例对象作为静态局部变量,然后增加一个辅助类,并声明一个该辅助类的类静态成员变量,在该辅助类的构造函数中,初始化单例对象。以下为代码
class Singleton
{
public:
static Singleton* GetInstance()
{
static Singleton instance;
return &instance;
} protected:
// 辅助代理类
struct Object_Creator
{
Object_Creator()
{
Singleton::GetInstance();
}
};
static Object_Creator _object_creator; Singleton() {}
~Singleton() {}
}; Singleton::Object_Creator Singleton::_object_creator;
首先,代理类这个外部变量初始化时,在其构造函数内部调用 Singleton::GetInstance();从而间接完成单例对象的初始化,这就通过该代理类实现了饿汉模式的特性。
其次,仍然考虑第三种模式的缺陷。 当A的初始化依赖于B, 【 即A的构造函数中要调用B::GetInstance() ,而此时B::m_instance 可能还未初始化,显然调用结果就是非法的 】 现在就变为【在A的构造函数中要调用B::GetInstance() ,如果B尚未初始化,就会引发B的初始化】,所以在不同编译单元内全局变量的初始化顺序不定的问题就随之解决。
最后,关于使用懒汉还是饿汉模式,我的理解:
如果这个单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取文件啊等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,也是一种资源浪费吧。 所以这种情况懒汉模式(延迟加载)更好。
如果这个单例对象在多线程高并发环境下频繁使用,性能要求较高,那么显然使用饿汉模式来避免资源竞争,提高响应速度更好。
C++ 单例模式的几种实现研究的更多相关文章
- Python 单例模式的几种实现方式
单例模式的几种实现方式 先来看几个魔法方法的简单运用:__new__, __init__, __call__. class A(object): def __init__(self, x): prin ...
- java设计模式之单例模式(几种写法及比较)
概念: Java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍三种:懒汉式单例.饿汉式单例.登记式单例. 单例模式有以下特点: 1.单例类只能有一个实例. 2.单例类必须自己创建 ...
- java单例模式的几种写法比较
概念: Java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍三种:懒汉式单例.饿汉式单例.登记式单例. 单例模式有以下特点: 1.单例类只能有一个实例. 2.单例类必须自己创建 ...
- JAVA中单例模式的几种实现方式
1 线程不安全的实现方法 首先介绍java中最基本的单例模式实现方式,我们可以在一些初级的java书中看到.这种实现方法不是线程安全的,所以在项目实践中如果涉及到线程安全就不会使用这种方式.但是如果不 ...
- Python中的单例模式的几种实现方式的优缺点及优化
单例模式 单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在.当你希望在整个系统中,某个类只能出现一个实例时,单例对象就能派上用场. ...
- python实现单例模式的三种方式及相关知识解释
python实现单例模式的三种方式及相关知识解释 模块模式 装饰器模式 父类重写new继承 单例模式作为最常用的设计模式,在面试中很可能遇到要求手写.从最近的学习python的经验而言,singlet ...
- day29单例模式的4种实现模式
单例模式的四种实现模式单例模式实现方式一: import settings class MySQL: __instance=None def __init__(self, ip, port): ...
- Python中的单例模式的几种实现方式的及优化
单例模式 单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在.当你希望在整个系统中,某个类只能出现一个实例时,单例对象就能派上用场. ...
- Java设计模式之单例模式(七种写法)
Java设计模式之单例模式(七种写法) 第一种,懒汉式,lazy初始化,线程不安全,多线程中无法工作: public class Singleton { private static Singleto ...
随机推荐
- crontab 定期拉取代码
* * * * * cd /home/wwwroot/default/lion/ && /usr/bin/git pull origin 5hao >> /tmp/git. ...
- Eclipse中安装插件的三种方式
转载自:http://www.cnblogs.com/lcgustc/archive/2013/01/03/2843496.html Eclipse也用了很久,但是都没有怎么去研究过怎么安装插件,今天 ...
- 【asp.net mvc】 扩展 htmlhelper 实现分页
参考文档:http://www.cnblogs.com/caofangsheng/p/5670071.html http://www.cnblogs.com/arte ...
- zk-web
Ref:https://github.com/qiuxiafei/zk-web zk-web是一个用clojure with noir and boostrap写的Zookeeper WEB UI管理 ...
- Redis 模糊匹配 SearchKeys
语法:KEYS pattern说明:返回与指定模式相匹配的所用的keys.该命令所支持的匹配模式如下:(1)?:用于匹配单个字符.例如,h?llo可以匹配hello.hallo和hxllo等:(2)* ...
- RSA host key has changed 错误
RSA host key for mysharebook.cn has changed and you have requested strict checking.Host key verifica ...
- 【51NOD-0】1089 最长回文子串 V2(Manacher算法)
[算法]回文树 #include<cstdio> #include<algorithm> #include<cstring> using namespace std ...
- vue装逼神器简述
主要是分享下用vuejs开发项目过程中遇到的问题,vuejs开发的优势和需要注意的地方. 项目主要页面:主页,最新,分类,分类列表,详情页,结果页,斗图(列表,制作页) 效果图: 地址:https:/ ...
- hdu 1159 Common Subsequence(最长公共子序列 DP)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1159 Common Subsequence Time Limit: 2000/1000 MS (Jav ...
- xxx_initcall相关知识
参考文件include/linux/init.h /* * Early initcalls run before initializing SMP. * * Only for built-in cod ...