C++单例实现
来源:互联网 发布:知乎 张国荣 编辑:程序博客网 时间:2024/06/05 15:03
单例本来是个很简单的模式,实现上应该也是很简单,但C++单例的简单实现会有一些坑,来看看为了避免这些坑怎样一步步演化到boost库的实现方式。
方案一
class QMManager{public: static QMManager &instance() { static QMManager instance_; return instance_; }}这是最简单的版本,在单线程下(或者是C++0X下)是没任何问题的,但在多线程下就不行了,因为static QMManager instance_;这句话不是线程安全的。
在局部作用域下的静态变量在编译时,编译器会创建一个附加变量标识静态变量是否被初始化,会被编译器变成像下面这样(伪代码):
static QMManager &instance(){ static bool constructed = false; static uninitialized QMManager instance_; if (!constructed) { constructed = true; new(&s) QMManager; //construct it } return instance_;}
这里有竞争条件,两个线程同时调用instance()时,一个线程运行到if语句进入后还没设constructed值,此时切换到另一线程,constructed值还是false,同样进入到if语句里初始化变量,两个线程都执行了这个单例类的初始化,就不再是单例了。
方案二
一个解决方法是加锁:
static QMManager &instance(){ Lock(); //锁自己实现 static QMManager instance_; UnLock(); return instance_;}
但这样每次调用instance()都要加锁解锁,代价略大。
方案三
那再改变一下,把内部静态实例变成类的静态成员,在外部初始化,也就是在include了文件,main函数执行前就初始化这个实例,就不会有线程重入问题了:
class QMManager{protected: static QMManager instance_; QMManager(); ~QMManager(){};public: static QMManager *instance() { return &instance_; } void do_something();};QMManager QMManager::instance_; //外部初始化
这被称为饿汉模式,程序一加载就初始化,不管有没有调用到。
看似没问题,但还是有坑,在一个2B情况下会有问题:在这个单例类的构造函数里调用另一个单例类的方法可能会有问题。
看例子:
//.hclass QMManager{protected: static QMManager instance_; QMManager(); ~QMManager(){};public: static QMManager *instance() { return &instance_; }}; class QMSqlite{protected: static QMSqlite instance_; QMSqlite(); ~QMSqlite(){};public: static QMSqlite *instance() { return &instance_; } void do_something();}; QMManager QMManager::instance_;QMSqlite QMSqlite::instance_;
//.cppQMManager::QMManager(){ printf("QMManager constructor\n"); QMSqlite::instance()->do_something();} QMSqlite::QMSqlite(){ printf("QMSqlite constructor\n");}void QMSqlite::do_something(){ printf("QMSqlite do_something\n");}
这里QMManager的构造函数调用了QMSqlite的instance函数,但此时QMSqlite::instance_可能还没有初始化。
这里的执行流程:程序开始后,在执行main前,执行到QMManager QMManager::instance_;这句代码,初始化QMManager里的instance_静态变量,调用到QMManager的构造函数,在构造函数里调用QMSqlite::instance(),取QMSqlite里的instance_静态变量,但此时QMSqlite::instance_还没初始化,问题就出现了。
那这里会crash吗,测试结果是不会,这应该跟编译器有关,静态数据区空间应该是先被分配了,在调用QMManager构造函数前,QMSqlite成员函数在内存里已经存在了,只是还未调到它的构造函数,所以输出是这样:
QMManager constructor
QMSqlite do_something
QMSqlite constructor
方案四
那这个问题怎么解决呢,单例对象作为静态局部变量有线程安全问题,作为类静态全局变量在一开始初始化,有以上2B问题,那结合下上述两种方式,可以解决这两个问题。boost的实现方式是:单例对象作为静态局部变量,但增加一个辅助类让单例对象可以在一开始就初始化。如下:
//.hclass QMManager{protected: struct object_creator { object_creator() { QMManager::instance(); } inline void do_nothing() const {} }; static object_creator create_object_; QMManager(); ~QMManager(){};public: static QMManager *instance() { static QMManager instance; return &instance; }};QMManager::object_creator QMManager::create_object_; class QMSqlite{protected: QMSqlite(); ~QMSqlite(){}; struct object_creator { object_creator() { QMSqlite::instance(); } inline void do_nothing() const {} }; static object_creator create_object_;public: static QMSqlite *instance() { static QMSqlite instance; return &instance; } void do_something();}; QMManager::object_creator QMManager::create_object_;QMSqlite::object_creator QMSqlite::create_object_;
结合方案3的.cpp,这下可以看到正确的输出和调用了:
QMManager constructor
QMSqlite constructor
QMSqlite do_something
来看看这里的执行流程:
初始化QMManager类全局静态变量create_object_
->调用object_creator的构造函数
->调用QMManager::instance()方法初始化单例
->执行QMManager的构造函数
->调用QMSqlite::instance()
->初始化局部静态变量QMSqlite instance
->执行QMSqlite的构造函数,然后返回这个单例。
跟方案三的区别在于QMManager调用QMSqlite单例时,方案3是取到全局静态变量,此时这个变量未初始化,而方案四的单例是静态局部变量,此时调用会初始化。
跟最初方案一的区别是在main函数前就初始化了单例,不会有线程安全问题。
最终boost
上面为了说明清楚点去除了模版,实际使用是用模版,不用写那么多重复代码,这是boost库的模板实现:
template <typename T>struct Singleton{ struct object_creator { object_creator(){ Singleton<T>::instance(); } inline void do_nothing()const {} }; static object_creator create_object; public: typedef T object_type; static object_type& instance() { static object_type obj; //据说这个do_nothing是确保create_object构造函数被调用 //这跟模板的编译有关 create_object.do_nothing(); return obj; } };template <typename T> typename Singleton<T>::object_creator Singleton<T>::create_object; class QMManager{protected: QMManager(); ~QMManager(){}; friend class Singleton<QMManager>;public: void do_something(){};}; int main(){ Singleton<QMManager>::instance()->do_something(); return 0;}
其实Boost库这样的实现像打了几个补丁,用了一些奇技淫巧,虽然确实绕过了坑实现了需求,但感觉挺不好的。
参考资料:
http://blog.csdn.net/fullsail/article/details/8483106
http://blog.cnbang.net/tech/2229/以上是为实现单例在多线程运行中正常运行而做的修改。
下面介绍一下基于boost库实现单例的简单使用方法。
为了让单例使用起来更为方便,可定义如下宏:
#define SINGLE(classname) Singleton<classname>::instance()
使用时,可直接调用:
SINGLE(MyClass)->...
总结:
本文介绍了单例的实现方法,并介绍了简单的单例封装方法,下一篇将介绍单例的管理。
- Objective-C单例实现
- Objective C 单例实现
- Objective C 中实现单例模式
- Objective C 实现Singleton(单例)模式.
- Objective-C实现单例模式
- 【Objective-C】单例模式的实现
- objective-C 实现单例模式
- 单例模式代码实现(C++)
- 设计模式--单例模式 C++实现
- 单例模式 (C语言实现)
- Objective-C实现单例模式
- Objective-C单例模式实现
- [Objective-C] 用 dispatch_once 实现单例
- Objective-C 单例模式的实现
- Objective-c 实现单例设计模式
- C 语言单例的一种实现
- c++--Singleton单例模式的实现
- 单例模式,C/C++实现
- 控制器view加载/ViewControl中View的创建
- 安卓系统for x86系统安装测试(一)
- [FormerlySerializedAs] 防止更新变量后数据丢失
- UITableView cell自动适应内容高度
- 程序员必看的书
- C++单例实现
- Redis实战之征服 Redis + Jedis + Spring (三) 分类: 开源应用系统 2013-08-03 11:07 2136人阅读 评论(0) 收藏 举报 一开始以为Spring下操作
- 高效的程序员是聪明和懒惰的
- oracle insert all
- Linux 内核学习(1)——内核目录结构
- HDU 2191 悼念512汶川大地震遇难同胞――珍惜现在,感恩生活 多重背包
- Cisco交换基础路由配置:交换机怎么设置实现三层交换功能
- 马踏飞燕(SDNU ACM-ICPC 2011复赛(2010级))
- Android Unity3D 逆向截取交互事件(二) 之逆向修改dll并加入Java的接口