应用实例:线程池,日志类,windows系统的任务管理器
一个单例模式应具备以下特征:
只能实例化同一个对象
可以全局访问
禁止拷贝
线程安全
针对第一条,可以将构造函数权限设为private,如果是public那么每次实例化调用构造函数,对象的内存地址都不同,也就是说只准调用一次构造函数。那问题来了,构造函数都private了,怎么实例化对象?显然要用某个public方法来调用,这又有问题:都没实例化对象,怎么调用public方法?所以这个public方法只能是静态的了。
针对第二条,全局性很容易想到静态函数,它是属于类的,而不是属于某个对象的。
第三条很容易,将拷贝构造函数和复制运算符声明为private即可。
综上,单例类的雏形应该是这样的:1 2 3 4 5 6 7 8 9 10 11 12 class Singleton { public : private : Singleton (){std::cout<<"单例构造" <<endl; } ~Singleton (){std::cout<<"单例析构" <<endl; } Singleton (const Singleton &); Singleton& operator =(const Singleton&); static Singleton* m; };
关键就是怎么实现public static方法。
测试1 首先想到这样的方法:1 2 3 4 5 static Singleton instance () { Singleton s; return s; }
结果报错构造函数和析构函数是private
,第一行就编译不过
测试2 测试下面这种方法1 2 3 4 5 6 7 8 9 static Singleton* instance1 () { Singleton *s = new Singleton (); return s; } Singleton* s1 = Singleton::instance1 (); Singleton *s2 = Singleton::instance1 (); delete s1;delete s2;
结果发现有两个构造函数,而且无法析构。
有缺陷的懒汉模式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Singleton { public : static Singleton* LazyInstance () { if (!m) m = new Singleton (); return m; } ~Singleton (){std::cout<<"单例析构" <<endl; } private : Singleton (){std::cout<<"单例构造" <<endl; } Singleton (const Singleton &)=delete ; Singleton& operator =(const Singleton&)=delete ; static Singleton* m; };
测试:1 2 3 4 5 6 7 Singleton* s = Singleton::LazyInstance (); std::cout<<s<<endl; Singleton* s2 = s; std::cout<<s2<<endl; delete s;
s和s2的内存地址相同,说明是单例。但类中只负责new出对象,却没有负责delete对象,结果发现只调用一次构造函数,还需要手动delete s
。可以用智能指针修正
如果有两个线程,假设pthread_1刚判断完 intance 为NULL 为真,准备创建实例的时候,切换到了pthread_2, 此时pthread_2也判断intance为NULL为真,创建了一个实例,再切回pthread_1的时候继续创建一个实例返回,那么此时就不再满足单例模式的要求
双检锁+智能指针的懒汉模式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class SingleTon { public : typedef boost::unique_ptr<SingleTon> Ptr; static Ptr getInstance () { if (instance==nullptr ) { std::lock_guard<std::mutex> lk (m_mutex) ; if (instance==nullptr ) instance = boost::shared_unique<SingleTon>(new SingleTon); } return instance; } ~SingleTon (){ qDebug ()<<"destruct" ; } private : SingleTon (){ qDebug ()<<"construct" ; } SingleTon (const SingleTon& s){} SingleTon& operator =(const SingleTon& s){} static std::mutex m_mutex; static Ptr instance; };
改用智能指针做静态类型,
1 2 3 4 5 6 SingleTon::Ptr s = SingleTon::getInstance (); qDebug () << s.get ();SingleTon::Ptr s1 = SingleTon::getInstance (); qDebug () << s1.get ();
结果二者地址相同,也运行了析构函数。 加了锁,使用互斥量来达到线程安全。这里使用了两个if判断语句的技术称为双检锁 ;好处是,只有判断指针为空的时候才加锁,避免每次调用getInstance
的方法都加锁,锁的开销毕竟还是有点大的。
缺点:使用智能指针会要求用户也得使用智能指针;使用锁也有开销; 同时代码量也增多了;在某些平台,双检锁会失效
饿汉模式 优点:不需要加锁,执行效率高,线程安全的
缺点:初始化即实例化,浪费内存1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Singelton { private : Singelton (){} static Singelton *single; public : static Singelton *GetSingelton () ; }; Singelton *Singelton::single = new Singelton; Singelton *Singelton::GetSingelton () { return single; }
Meyers模式 1 2 3 4 5 6 #define App SingleTon::MeyersInstance() static SingleTon& MeyersInstance () { static SingleTon s; return s; }
这种方式所用到的特性是在C++11标准中的Magic Static 特性。如果当变量在初始化的时候,并发同时进入声明语句,并发线程将会阻塞等待初始化结束。这样保证了并发线程在获取静态局部变量的时候一定是初始化过的,所以具有线程安全性。
在 MeyersInstance() 函数内定义局部静态变量的好处是,构造函数只会在第一次调用MeyersInstance() 时被初始化, 保证了成员变量和 Singleton 本身的初始化顺序。 它还有一个潜在的安全措施, MeyersInstance() 返回的是对局部静态变量的引用, 如果返回的是指针, MeyersInstance() 的调用者很可能会误认为他要检查指针的有效性, 并负责销毁。
Qt中的全局指针 Qt里有一个全局指针qApp
,在任意地方都能使用,看看是不是单例模式。
QApplication中:1 #define qApp (static_cast<QApplication *> (QCoreApplication::instance()))
QCoreapplication中:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #define qApp QCoreApplication::instance() static QCoreApplication *instance () { return self; }static QCoreApplication *self;QCoreApplication *QCoreApplication::self = 0 ; QCoreApplication::QCoreApplication () { d_func ()->q_ptr = this ; d_func ()->init (); QCoreApplicationPrivate::eventDispatcher->startingUp (); } void QCoreApplicationPrivate::init () { ...... Q_ASSERT_X (!QCoreApplication::self, "QCoreApplication" , "there should be only one application object" ); QCoreApplication::self = q; ...... }
从QCoreapplication来看,qApp是个宏,实际是函数QCoreApplication::instance()
,QCoreApplication这个类十分关键,构造函数肯定不能是private。从这个self来看,特别像懒汉模式,self是在QCoreApplication构造函数里赋值,赋给它的q指针实际就是QCoreApplication的this指针。
但是在程序里使用qApp,你会发现其地址都一样,也就是同一个全局指针,这就在于Q_ASSERT_X这句限定了只能有一个QCoreApplication对象,再加上拷贝构造函数和赋值运算符都在QObject限定为private,因此qApp也是一种单例模式 。所以我们可以说单例模式不一定限定构造和析构是private,这个使用了Qt特有的d指针和q指针,技巧性太高,还是用meyers模式吧
ROS中的单例模式 看ROS源码中的类TopicManager
,它用到了单例模式,我模仿写了一个类如下:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class SingleTon ;typedef boost::shared_ptr<SingleTon> SingleTonPtr;#define Ptr SingleTon::getInstance() class SingleTon { public : SingleTon () {} static const SingleTonPtr& getInstance () { static SingleTonPtr f = boost::make_shared<SingleTon>(); return f; } void out () { cout<<" out put " <<endl; } private : int m_num; };
结果发现构造函数只能是public,如果是private,就会报错,原因在make_shared
中。这样一来就不能实现单例了,看来这种做法不可行。
参考:探究 C++ Singleton(单例模式)