前言
单例模式在实际应用中被广泛使用,其意图是保证一个类只有一个实例。在网上查阅了部分资料,发现用C++写单例模式考虑的问题挺多的,当然目前C++11的一些特性基本解决了原来出现的问题(C#和Java自身的机制保证基本不会出现C++中的问题),这里记录下。
单例模式实现
1. 概要
2. 懒汉式
//懒汉式(存在内存泄漏问题,且非线程安全)
class Singleton_lazy
{
public:
static Singleton_lazy* getInstance(){
if(instance == nullptr){
instance = new Singleton_lazy();
}
return instance;
}
private:
Singleton_lazy(){}//构造函数私有
~Singleton_lazy(){}
Singleton_lazy(const Singleton_lazy&){} //拷贝构造私有
Singleton_lazy &operator=(const Singleton_lazy&){} //赋值构造私有
static Singleton_lazy* instance; //实例静态化
};
//初始化懒汉模式的静态成员变量
Singleton_lazy *Singleton_lazy::instance = nullptr;
上述方法中存在着内存泄漏的问题(主要体现在Singleton_lazy::instance指针释放),解决方法有两种:
(1)使用智能指针(C++11特性)
(2)使用静态的嵌套类对象
对于第二种使用嵌套类的方法,代码如下:
//懒汉式(使用静态的嵌套类方法避免内存泄漏,仍然非线程安全)
class Singleton_lazy
{
public:
static Singleton_lazy* getInstance(){
if(instance == nullptr){
instance = new Singleton_lazy();
}
return instance;
}
private:
Singleton_lazy(){}//构造函数私有
~Singleton_lazy(){}
Singleton_lazy(const Singleton_lazy&){} //拷贝构造私有
Singleton_lazy &operator=(const Singleton_lazy&){} //赋值构造私有
static Singleton_lazy* instance; //实例静态化
private:
//使用嵌套的静态类(新增部分)
class Deletor{
public:
~Deletor(){
if(Singleton_lazy::instance != NULL){
delete Singleton_lazy::instance;
}//if
} //~Deletor
};//class
static Deletor deletor;
};
//初始化懒汉模式的静态成员变量
Singleton_lazy *Singleton_lazy::instance = nullptr;
当然,如果要考虑线程安全,需要利用同步机制进行处理,其中的一种同步的方法是双重校验锁
(DCL: Double-Checked Locking Pattern),代码如下:
#include <mutex>
//懒汉模式的线程安全方法
std::mutex mt;
class Singleton_lazySafeThread
{
public:
static Singleton_lazySafeThread* getInstance(){
if(instance == nullptr){
mt.lock();
if(instance == nullptr){
instance = new Singleton_lazySafeThread();
}
mt.unlock();
}
return instance;
}
private:
Singleton_lazySafeThread(){}//构造函数私有
~Singleton_lazySafeThread(){}
Singleton_lazySafeThread(const Singleton_lazySafeThread&){} //拷贝构造私有
Singleton_lazySafeThread &operator=(const Singleton_lazySafeThread&){} //赋值构造私有
static Singleton_lazySafeThread* instance; //实例静态化
};
//初始化懒汉模式的静态成员变量
Singleton_lazySafeThread *Singleton_lazySafeThread::instance = nullptr;//懒汉模式(双重校验锁,线程安全)
但是,看网上的资料说,在C++11出来之前,由于编译器优化的原因(需要靠内存屏障去解决),导致instance虽然已经不等于nullptr,但是此时instance并没有初始化,此时会出现不同步的严重问题,解决方法除了内存屏障的指令,还可以用原子操作保护内存同步(C++11提供了这种机制)。
另外,在C++11中,懒汉模式有一种非常优雅的写法
,能够解决内存同步和线程的问题(其实就是返回一个引用,不是指针),代码如下:
class Singleton
{
private:
Singleton() { };
~Singleton() { };
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
public:
static Singleton& getInstance() //返回引用
{
static Singleton instance;
return instance;
}
};
返回引用的好处是没有没有用到指针,也就是不用考虑手动delete。
3. 饿汉式
//饿汉模式(立即初始化,没有线程安全问题),但是注意在合适的时候delete(如果用到指针的话)
//当然,下面的例子是用的返回引用的方式
class Singleton_eager
{
public:
static Singleton_eager& getInstance(){//没有用指针
return instance;
}
private:
Singleton_eager(){}//构造函数私有
~Singleton_eager(){}
Singleton_eager(const Singleton_eager&){} //拷贝构造私有
Singleton_eager &operator=(const Singleton_eager&){} //赋值构造私有
static Singleton_eager instance; //实例静态化
};
//初始化饿汉模式的静态成员变量
Singleton_eager Singleton_eager::instance;
当然,这种饿汉模式也存在一些潜在的问题,主要体现在实例化对象的顺序可能会导致实例化对象不成功。
4. 设计模式总结
总之,C++11之后,内存模型方面做了不少的优化,推荐使用返回引用的方式。
懒汉模式和饿汉模式的场景可以按照以下原则:
(1)懒汉模式
是时间换取空间,也就是不要万不得已,不去实例化对象,适用于访问量不大,但是追求效率的场景下
(2)饿汉模式
是空间换取时间,在访问量大或线程较多的场景下用
另外,C++中的一些设计模式的总结导航,可以参考这里(设计模式总结)。
参考资料
[1] https://zhuanlan.zhihu.com/p/37469260
[2] https://www.cnblogs.com/chengjundu/p/8473564.html