C++设计模式——单例模式
概述
单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中一个类只有一个实例。即一个类只有一个对象实例
单例模式的实现大概可以分为懒汉式实现和饿汉式实现。顾名思义,懒汉式就说明它很懒,只有在使用的时候才去创建对象。而饿汉式说明它很饿,还没开始使用就迫不及待的将对象创建出来了。
饿汉式实现
//Singleton.h
#ifndef SIGLETON_H_
#define SIGLETON_H_
class Singleton
{
public:
static Singleton* GetInstance(); //公有的获取对象的接口
private:
Singleton(){} //私有的构造函数
static Singleton* m_instance;
};
#endif
//Singleton.cpp
#include "Singleton.h"
Singleton* Singleton::m_instance = new Singleton();
Singleton* Singleton::GetInstance()
{
return m_instance;
}
饿汉式实现的优缺点
优点
无须担心多线程的问题。
不需要加锁,执行效率比较高
缺点
- 在类刚加载的时候,就创建了该单例,消耗内存。
懒汉式实现
//Singleton.h
#ifndef SIGLETON_H_
#define SIGLETON_H_
class Singleton
{
public:
static Singleton* GetInstance();//公有的获取对象的接口
private:
Singleton(){} //私有的构造函数
static Singleton* m_instance;
};
#endif
//Singleton.cpp
#include "Singleton.h"
Singleton* Singleton::m_instance = NULL;
Singleton* Singleton::GetInstance()
{
if (NULL == m_instance)
{
m_instance = new Singleton();
}
return m_instance;
}
懒汉式实现优缺点
优点
- 不会在类刚加载的时候就初始化,能节省内存消耗
缺点
- 多线程不安全,必须要通过加锁来保证多线程安全,这样对运行效率有点影响。
懒汉式实现优化
使用加锁来实现懒汉式单例模式.
//Singleton.h
#ifndef SIGLETON_H_
#define SIGLETON_H_
#include <iostream>
#include <mutex> //C++11
class Singleton
{
public:
static Singleton* GetInstance();
private:
Singleton(){} //私有的构造函数
static Singleton* m_instance;
static mutex m_mutex; //锁
};
#endif
//Singleton.cpp
#include "Singleton.h"
Singleton* Singleton::m_instance = NULL;
mutex Singleton::m_mutex;
Singleton* Singleton::GetInstance()
{
if (NULL == m_instance)
{
std::lock_guard<std::mutex> lock(m_mutex); //在这儿上锁判断,只有多线程访问的时候才会执行上锁解锁,有效的缓解了加锁引起的效率问题。
if (NULL == m_instance)
{
m_instance = new Singleton();
}
}
return m_instance;
}
内存释放问题
说了这么多,关于懒汉式单例模式创建之后的释放问题一直没有解决,我们在第一次初始化的时候创建出来,什么时候去释放它呢?提到这儿,懒汉式单例模式针对资源释放又衍生出了另外的四种实现方式。
1.局部静态变量返回引用实现
#ifndef SIGLETON_H_
#define SIGLETON_H_
#include <iostream>
class Singleton
{
public:
static Singleton& GetInstance()
{
static Singleton instance; //为了说明问题,忽略多线程。实际实现加锁和前面的懒汉式实现相同。
return instance;
}
private:
Singleton(){} //私有的构造函数
};
如此一来,对于单例的释放问题就很简单的解决了,但是如果代码中出现这样使用单例的:
Singleton instance = Singleton::GetInstance();
由于编译器在编译的时候,会自动生成拷贝构造函数,如果这样使用,会调用类的拷贝构造函数,从而制造出另外一个单例对象,这对于单例模式的初衷是矛盾的。因此,针对该问题,有了两种解决办法。
第一种解决办法,也就是懒汉式单例模式实现的第二种方法
2.局部静态变量返回指针实现
#ifndef SIGLETON_H_
#define SIGLETON_H_
#include <iostream>
class Singleton
{
public:
static Singleton* GetInstance()
{
static Singleton instance; //为了说明问题,忽略多线程。实际实现加锁和前面的懒汉式实现相同。
return &instance;
}
private:
Singleton(){} //私有的构造函数
};
这样就解决了调用默认拷贝构造函数引起的问题了。同时还有另外的一种实现,既然是编译器在发现类没实现的时候给默认添加了拷贝构造函数,那我们只需要把拷贝构造函数禁止掉就可以了。所以有了如下实现
3.局部静态变量返回引用,禁止拷贝构造函数实现
#ifndef SIGLETON_H_
#define SIGLETON_H_
#include <iostream>
class Singleton
{
public:
static Singleton& GetInstance()
{
static Singleton instance; //为了说明问题,忽略多线程。实际实现加锁和前面的懒汉式实现相同。
return instance;
}
private:
Singleton(){} //私有的构造函数
Singleton(const Singleton&);//只声明,不实现
Singleton& operator = (const Singleton&);//只声明,不实现
};
这样就有效的避免了在使用单例的时候,无意中调用了拷贝构造函数。当然还有一种高大上的自动释放的实现方式是使用GC机制。
4.使用GC机制的单例实现
class Singleton
{
public:
static Singleton* GetInstance()
private:
class GC
{
public:
~GC()
{
if (Singleton::m_instance != NULL)
{
delete m_instance;
m_instance = NULL;
}
}
}
static Singleton* m_instance;
static GC gc;
};
在使用的时候,必须要声明Singleton::GC:
Singleton::GC Singleton::GC::gc;//必须声明
//...
//do something
//...
如此使用,当你程序结束的时候,就会调用调用GC的析构函数,GC的析构函数会将你的资源释放掉。实现自动释放的功能。
到这儿,五种创建型设计模式已经介绍完了:
1.简单工厂模式(不属于24种设计模式之一)
2.工厂方法模式
3.抽象工厂模式
4.建造者模式
5.原型模式
6.单例模式