title: 设计模式之单例模式
longzy:2019-2-23
单列模式,顾名思义,一个类只有一个实例。所以单列模式的特征为:
- 只有一个实例
- 必须提供一个全局访问点(静态成员方法或静态静态成员变量)
- 不可以复制拷贝
- 如果得到是静态成员对象指针的地址,必须提供一个释放指针的方法
根据以上描述的特征,那么一个简单的单例模式就诞生了,如下代码所示
template <typename T>
class Singleton
{
public:
static T* Instance()
{
if (m_pInstance == nullptr)
m_pInstance = new T();
return m_pInstance;
}
static void DestroyInstance()
{
if (m_pInstance != nullptr)
{
delete m_pInstance;
m_pInstance = nullptr;
}
}
private:
Singleton(){}
~Singlento(){}
Singleton(const Singleton&);
Singleto& operator = (const Singleton&);
private:
static T* m_pInstance;
};
template <typename T>
T* Singleton<T>::m_pInstance = nullptr;
我们分析下上面代码,如果在单线程环境下面运行,没有什么问题,假设在多线程环境中,两个线程同时运行到m_pInstance == nullptr,此时条件为真,那么就会创建两个实例,这不符合单列的特征,那么在这里需要改进,在改进前,我们先用c++11实现一个不可复制的类Noncopyable,以后让其继承该类即可
class Noncopyable
{
protected:
Noncopyable() = default;
~Noncopyable() = default;
Noncopyable(const Noncopyable&) = delete;
Noncopyable& operator = (const Noncopyable&) = delete;
};
然后改进后的单列类如下
#include <mutex>
template <typename T>
class Singleton : Noncopyable
{
public:
static T* Instance()
{
std::lock_guard<std::mutex> lock(m_mutex);
if (m_pInstance == nullptr)
{
m_pInstance = new T();
}
return m_pInstance;
}
static void DestroyInstance()
{
std::lock_guard<std::mutex> lock(m_mutex);
if (m_pInstance != nullptr)
{
delete m_pInstance;
m_pInstance = nullptr;
}
}
private:
static T* m_pInstance;
static std::mutex m_mutex;
};
template <typename T>
T* Singleton<T>::m_pInstance = nullptr;
template <typename T>
std::mutex Singleton<T>::m_mutex;
我们分析下上面代码,解决线程安全问题无非就是加锁,但是如果线程很多情况下,每个线程都要等拿到锁的线程运行结束后才继续执行,这样无疑会导致大量的线程阻塞,那么该如何解决呢,解决办法就是在锁之前先判断是否为nullptr,于是改进后的代码如下:
#include <mutex>
template <typename T>
class Singleton : Noncopyable
{
public:
static T* Instance()
{
if (m_pInstance == nullptr)
{
std::lock_guard<std::mutex> lock(m_mutex);
if (m_pInstance == nullptr)
{
m_pInstance = new T();
}
}
return m_pInstance;
}
static void DestroyInstance()
{
if (m_pInstance != nullptr)
{
std::lock_guard<std::mutex> lock(m_mutex);
if (m_pInstance != nullptr)
{
delete m_pInstance;
m_pInstance = nullptr;
}
}
}
private:
static T* m_pInstance;
static std::mutex m_mutex;
};
template <typename T>
T* Singleton<T>::m_pInstance = nullptr;
template <typename T>
std::mutex Singleton<T>::m_mutex;
继续分析上面的代码,其实这就是所谓的双检锁机制。但是请注意,如果数据量很大的情况,加锁释放锁本来就是耗时的操作,所以在大数据情况下,这种双检锁机制的单列模式性能就显得堪忧了,所以我们应该避免加锁操作,于是就出现了另外一种单列模式
template <typename T>
class Singleton : Noncpyable
{
public:
static T* Instance()
{
return m_pInstance;
}
private:
static T* m_pInstance;
};
template <typename T>
T* Singleton<T>::m_pInstance = new T();
继续分析上面代码,巧妙的使用了静态成员初始化的特性,静态成员初始化是在程序进入主函数之前,主线程以单线程的方式完成了初始化操作,所以很好的解决了线程安全问题。但是没有提供一个销毁静态实例的方法。于是我们可以考虑返回静态成员对象的地址,然后利用对象的自动销毁功能来做释放操作,于改进后的代码如下:
template <typename T>
class Singleton : Noncopyable
{
public:
static T* Instance()
{
return &m_Instance;
}
private:
static T m_Instance;
};
template <typename T>
T Singleton<T>::m_Instance;
这样的实现应该算是比较好了,但是还有比这更好的完美方法,利用linux的pthread_once和c++11的std::call_once
template <typename T>
class Singleton : Noncopyable
{
public:
static T* Instance()
{
std::call_once(m_flag,&Singleton::Init); //c++11
//pthread_once(&m_ponce,&Singleton::Init); //linux
return m_pInstance;
}
static void DestroyInstance()
{
delete m_pInstance;
m_pInstance = nullptr;
}
private:
static T* m_pInstance;
static std::once_flag m_flag;
//static pthread_once_t m_ponce;
static void Init()
{
m_pInstance = new T();
}
};
//template <typename T>
//pthread_once_t Singleton<T>::m_ponce = PTHREAD_ONCE_INIT;
template <typename T>
T* Singleton<T>::m_pInstance = nullptr;
另外放一个超级大招,利用c++11的可变模板参数,放一个万能的单列模式
template <typename T>
class Singleton : Noncopyable
{
public:
template <typename... Args>
static T* Instance(Args... args)
{
std::call_once(m_flag,&Singleton::Init,args);
return m_pInstance;
}
static void DestroyInstance()
{
delete m_pInstance;
m_pInstance = nullptr;
}
private:
static T* m_pInstance;
static std::once_flag = m_flag;
template <typename... Args>
static void Init(Args&&.. args)
{
return new T(std::forward<Args>(args)...);
}
};
template <typename T>
T* Singleton<T>::m_pInstance = nullptr;
template <typename T>
std::once_flag Singleton<T>::m_flag;
好了,单列模式就分析到这里了,最后一种是万金油。
上一个测试代码吧
#include <iostream>
#include "Singleton4.hpp"
struct A
{
A()
{
std::cout << "A is constrcut!" << std::endl;
num = 0;
}
~A()
{
std::cout << "A is desconstruct!" << std::endl;
}
int num;
void add(int n)
{
num = num + n;
}
int getNum()
{
return num;
}
};
struct B
{
int a_;
explicit B(int a) : a_(a)
{
}
int get()
{
return a_;
}
};
int main()
{
A *a = Singleton<A>::Instance();
A *b = Singleton<A>::Instance();
std::cout << "a is" << a << std::endl;
std::cout << "b is" << b << std::endl;
a->add(5);
std::cout << "b.num=" << b->getNum() << std::endl;
b->add(10);
std::cout << "a.num=" << a->getNum() << std::endl;
std::cout << "b.num=" << b->getNum() << std::endl;
B *bb = Singleton<B>::Instance(5);
std::cout << bb << std::endl;
std::cout << "bb.a=" << bb->get() << std::endl;
}
由于本人水平有限,若有错误,欢迎指出,谢谢!