目录
1、单例模式定义
1.1定义
单例 Singleton 是设计模式的一种,其特点是只提供唯一一个类的实例,具有全局变量的特点,在任何位置都可以通过接口获取到那个唯一实例;单例模式中会区分懒汉和饿汉模式,懒汉模式的意思是系统初始化时候并不new对象,饿汉模式的意思是系统初始化的时候就new一个对象,具体应用时,根据需求采用哪种模式。
1.2应用场景
- 设备管理器,系统中可能有多个设备,但是只有一个设备管理器,用于管理设备驱动;
- 数据池,用来缓存数据的数据结构,需要在一处写,多处读取或者多处写,多处读取;
- 缓存,读取一个或者多个缓存。
1.3什么情况下应该使用单例
根据stackoverflow上的一个高票答案 singleton-how-should-it-be-used:
You need to have one and only one object of a type in system
==你需要系统中只有唯一一个实例存在的类的全局变量的时候才使用单例==。
-
如果使用单例,应该用什么样子的
How to create the best singleton:
- The smaller, the better. I am a minimalist
- Make sure it is thread safe
- Make sure it is never null
- Make sure it is created only once
- Lazy or system initialization? Up to your requirements
- Sometimes the OS or the JVM creates singletons for you (e.g. in Java every class definition is a singleton)
- Provide a destructor or somehow figure out how to dispose resources
- Use little memory
==越小越好,越简单越好,线程安全,内存不泄露==
1.4构建单例模式需要注意的地方
对于使用单例模式,主要需要注意的两个地方是(1)内存泄露(2)线程安全,对于单例模式的内存释放问题,参考3的方式4中给出了一种解决方法,通过定义嵌套类来实现内存的自动释放问题,但需要注意的是期代码也是非线程安全的,如果在多线程中使用,需要考虑加锁。
2、单例模式的实现
2.1单例多种实现方式
2.1.1实现方式1
以下代码实现了简单的单例模式,但是有两个问题(1)线程不安全(2)内存泄漏
#include <iostream>
// version1:
// with problems below:
// 1. thread is not safe
// 2. memory leak
class Singleton{
private:
Singleton(){
std::cout<<"constructor called!"<<std::endl;
}
Singleton(Singleton&)=delete;
Singleton& operator=(const Singleton&)=delete;
static Singleton* m_instance_ptr;
public:
~Singleton(){
std::cout<<"destructor called!"<<std::endl;
}
static Singleton* get_instance(){
if(m_instance_ptr==nullptr){
m_instance_ptr = new Singleton;
}
return m_instance_ptr;
}
void use() const { std::cout << "in use" << std::endl; }
};
Singleton* Singleton::m_instance_ptr = nullptr;
int main(){
Singleton* instance = Singleton::get_instance();
Singleton* instance_2 = Singleton::get_instance();
return 0;
}
2.1.2实现方式2
以下实现通过智能指针避免了内存泄露,通过增加互斥锁解决了线程安全的问题,但是有如下缺点
(1)要求用户(使用者)也得使用智能指针
(2)某些情况下会使得双检锁失效。(在参考2中使用了mfc的锁机制,但原理是一样的)
#include <iostream>
#include <memory> // shared_ptr
#include <mutex> // mutex
// version 2:
// with problems below fixed:
// 1. thread is safe now
// 2. memory doesn't leak
class Singleton{
public:
typedef std::shared_ptr<Singleton> Ptr;
~Singleton(){
std::cout<<"destructor called!"<<std::endl;
}
Singleton(Singleton&)=delete;
Singleton& operator=(const Singleton&)=delete;
static Ptr get_instance(){
// "double checked lock"
if(m_instance_ptr==nullptr){
std::lock_guard<std::mutex> lk(m_mutex);
if(m_instance_ptr == nullptr){
m_instance_ptr = std::shared_ptr<Singleton>(new Singleton);
}
return m_instance_ptr;
}
}
private:
Singleton(){
std::cout<<"constructor called!"<<std::endl;
}
static Ptr m_instance_ptr;
static std::mutex m_mutex;
};
// initialization static variables out of class
Singleton::Ptr Singleton::m_instance_ptr = nullptr;
std::mutex Singleton::m_mutex;
int main(){
Singleton::Ptr instance = Singleton::get_instance();
Singleton::Ptr instance2 = Singleton::get_instance();
return 0;
}
2.1.3实现方式3(局部静态变量)
这种方法又叫做 Meyers' SingletonMeyer's的单例, 是著名的写出《Effective C++》系列书籍的作者 Meyers 提出的。所用到的特性是在C++11标准中的Magic Static特性:
If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.
如果当变量在初始化的时候,并发同时进入声明语句,并发线程将会阻塞等待初始化结束。
这样保证了并发线程在获取静态局部变量的时候一定是初始化过的,所以具有线程安全性。该种创建方式的特点是
(1)通过局部静态变量的特性保证了线程安全 (C++11, GCC > 4.3, VS2015支持该特性);
(2)不需要使用共享指针,代码简洁;
(3)注意在使用的时候需要声明单例的引用 Single&
才能获取对象。
#include <iostream>
class Singleton
{
public:
~Singleton(){
std::cout<<"destructor called!"<<std::endl;
}
Singleton(const Singleton&)=delete;
Singleton& operator=(const Singleton&)=delete;
static Singleton& get_instance(){
static Singleton instance;
return instance;
}
private:
Singleton(){
std::cout<<"constructor called!"<<std::endl;
}
};
int main(int argc, char *argv[])
{
Singleton& instance_1 = Singleton::get_instance();
Singleton& instance_2 = Singleton::get_instance();
return 0;
}
2.2扩展---多例实现
针对某些场景可能有多个共享资源被其他程序访问,这里给出了一种实现方法,以下方法没有考虑线程安全和内存释放的问题。
#include <string>
#include <stdio.h>
#include <map>
#include <iostream>
using namespace std;
//缓存的实例个数
const static int NUM_MAX = 5;
class Singleton;
static std::map<int, Singleton*> myMap = std::map<int, Singleton*>();
class Singleton {
private:
Singleton() {
m_singer = NULL;
cout << "正在构建Single" << endl;
}
public:
static Singleton* getInstance() {
m_singer = myMap[m_InstanceCount];
if (m_singer == NULL) {
m_singer = new Singleton;
myMap[m_InstanceCount] = m_singer;
}
m_InstanceCount++;
if (m_InstanceCount > NUM_MAX) {
m_InstanceCount = 1;
}
return m_singer;
}
private:
static Singleton *m_singer;
static int m_InstanceCount;//存放实例的个数
};
Singleton *Singleton::m_singer = NULL;
int Singleton::m_InstanceCount = 1;//初始化的实例个数
int main() {
Singleton* p1 = Singleton::getInstance();
Singleton* p2 = Singleton::getInstance();
Singleton* p3 = Singleton::getInstance();
Singleton* p4 = Singleton::getInstance();
Singleton* p5 = Singleton::getInstance();
printf("p1=%x,p2=%x,p3=%x,p4=%x,p5=%x\n", p1, p2, p3, p4, p5);
Singleton* p6 = Singleton::getInstance();
Singleton* p7 = Singleton::getInstance();
Singleton* p8 = Singleton::getInstance();
Singleton* p9= Singleton::getInstance();
Singleton* p10 = Singleton::getInstance();
printf("p6=%x,p7=%x,p8=%x,p9=%x,p10=%x\n", p6, p7, p8, p9, p10);
system("pause");
return 0;
}
3参考:
(1)https://www.cnblogs.com/sunchaothu/p/10389842.html
(2)《C++ 设计模式理论与实战大全-架构师必学视频课程》
(3)https://blog.csdn.net/linuxwuj/article/details/81187564(内存泄露的问题)