在开始学习之前,先简单讲讲对于设计模式的概念,设计模式是一些代码的写法,并非什么新奇的技术,是一些前人的模块化编程的经验总结,是一种设计程序的思想或者说思路模板。用设计模式写出来的代码比较晦涩,维护、接手的时候都有一定难度。设计模式有它的优点,要活学活用,不要深陷其中,正确的认识它。所有实用的编程方法和技术,是从需求出发的。
单例设计模式
需求:整个项目中,有某个或者某些特殊的类,属于该类的对象,只能创建一个。整个项目内都不能被第二次生成。
应用实例:
- 一个班级只有一个班主任。
- Windows是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
- 一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。
优点: - 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。
- 避免对资源的多重占用(比如写文件操作)。
缺点:不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
单例模式是使用频率比较高的一种设计模式。常用单例类,网上java的比较多,这里给出C++的代码。
#include <iostream>
class SingleClass
{
private:
//私有化构造函数 使其无法被外部创建
SingleClass() {}
private:
//私有静态成员指针,这里存储未来的唯一对象
static SingleClass* m_instance;
public:
//获取该对象的方法
static SingleClass *GetInstance()
{
//仅创建一次的对象
if (m_instance == NULL)
{
//对象被实例化
m_instance = new SingleClass();
}
return m_instance;
}
void testFunc()
{
std::cout << "test" << std::endl;
}
};
SingleClass *SingleClass::m_instance = NULL;
int main()
{
using namespace std;
//获取该类对象的指针
SingleClass *p_a = SingleClass::GetInstance();//创建这个对象
SingleClass *p_b = SingleClass::GetInstance();
cout << p_a << endl;
cout << p_b << endl;
return 0;
}
输出结果:
可以看到两个不同的指针,经过两次调用Get::Instance后指向的是相同的变量,此后在整个项目中,不论怎么调用SingleClass的GetInstance,返回都是唯一的这个对象,永远不可能有第二个。
有经验的读者可能会想到一个问题,这里单例类使用了静态成员指针,当需要的时候,我们怎样才能正确的释放它呢?使用智能指针来包装这个指针是一种办法。这里介绍另一种方法,清理类。
直接释放静态成员指针会导致内存泄漏,在析构函数中释放也会异常,本质上是操作系统替我们回收了
在SingleClass中写一个类,这个类只有一个作用,就是释放SingleClass的指针。在创建SingleClass对象的时候同时实例化一个静态清理类,修改代码如下:
#include <iostream>
class SingleClass
{
private:
//私有化构造函数 使其无法被外部创建
SingleClass() {}
private:
//私有静态成员指针,这里存储未来的唯一对象
static SingleClass* m_instance;
public:
//获取该对象的方法
static SingleClass *GetInstance()
{
//仅创建一次的对象
if (m_instance == NULL)
{
//对象被实例化
m_instance = new SingleClass();
//实例化清理类
static Release_this rt;
}
return m_instance;
}
//清理类
class Release_this
{
public:
~Release_this()
{
if (SingleClass::m_instance)
{
delete SingleClass::m_instance;
SingleClass::m_instance = NULL;
}
}
};
void testFunc()
{
std::cout << "test" << std::endl;
}
};
SingleClass *SingleClass::m_instance = NULL;
int main()
{
using namespace std;
//获取该类对象的指针
SingleClass *p_a = SingleClass::GetInstance();//创建这个对象
SingleClass *p_b = SingleClass::GetInstance();
cout << p_a << endl;
cout << p_b << endl;
//使用这个对象,初始化我们需要的数据
p_a->testFunc();
SingleClass::GetInstance()->testFunc();
//这两种方式是一样的
return 0;
}
这样就能在这个清理类析构的时候同时释放掉静态指针。
单例设计模式共享数据问题
多线程编程中面临的问题:需要我们在自己创建的线程(不是主线程)中来实例化单例对象,这种线程可能不止一个(最少2个),这个时候GetInstance需要互斥使用,我们将GetInstance的代码修改如下:
static SingleClass *GetInstance()
{
std::unique_lock<std::mutex> my_unique(my_mutex);
if (m_instance == NULL)
{
m_instance = new SingleClass();
static Release_this rt;
}
return m_instance;
}
这个时候就要被老板同事吐槽了,就这?每次GetInstance都要上道锁,不卡死?
那再改:
static SingleClass *GetInstance()
{
if (m_instance == NULL)
{
std::unique_lock<std::mutex> my_unique(my_mutex);
m_instance = new SingleClass();
static Release_this rt;
}
return m_instance;
}
老板:你明天不用来了
这样写有一个隐患,在多线程环境下,第一个线程已经进入if判断了,第二个线程也进入if判断了,那这个锁其实已经失效了,因为第一个new之后第二个还是会new,这里就需要使用双重锁定(检查):
static SingleClass *GetInstance()
{
if (m_instance == NULL)//双重锁定
{
std::unique_lock<std::mutex> my_unique(my_mutex);
if (m_instance == NULL)
{
m_instance = new SingleClass();
static Release_this rt;
}
}
return m_instance;
}
双重检查有一个细节,就是
if (m_instance != NULL)条件成立,则肯定表示m_instance已经被new过了
if (m_instance == NULL)条件成立,不代表m_instance一定没被new过
,只有m_instance在加完锁的条件下不为空,才能证明它确实不空。
std::call_once()
c++11引入的函数,该函数第二个参数是一个函数名a();
功能:保证函数a只能被调用一次。call_once具备互斥的能力,而且高效。
call_once需要一个标记结合使用,这个标记数std::once_flag,call_once就是通过这个标记来决定对应的函数a()是否执行,调用call_once成功后,call_once就把这个标记设置为一种已调用的,后续再次调用call_once,只要once_flag被设置为“已调用”,那么a()就不会再执行了。
现在我们利用call_once来改造单例类。
#include <iostream>
#include <mutex>
#include <thread>
std::once_flag g_flag;
class SingleClass
{
//使用一个只会被调用一次的函数来创建单例对象
static void CreateInstace()
{
m_instance = new SingleClass();
static Release_this rt;
}
private:
//私有化构造函数
SingleClass() {}
private:
static SingleClass* m_instance;
public:
static SingleClass *GetInstance()
{
std::call_once(g_flag, CreateInstace);
return m_instance;
}
class Release_this
{
public:
~Release_this()
{
if (SingleClass::m_instance)
{
delete SingleClass::m_instance;
SingleClass::m_instance = NULL;
}
}
};
//输出测试
void testFunc()
{
std::cout << SingleClass::GetInstance() << "地址来源于线程:" << std::this_thread::get_id() << std::endl;
}
};
//线程入口函数
void threadFunc()
{
std::cout <<"线程开始执行 id:" << std::this_thread::get_id() << std::endl;
SingleClass* p_a = SingleClass::GetInstance();
p_a->testFunc();
}
//初始化指针
SingleClass *SingleClass::m_instance = NULL;
int main()
{
using namespace std;
//获取该类对象的指针
thread my_thread1(threadFunc);
thread my_thread2(threadFunc);
my_thread1.join();
my_thread2.join();
return 0;
}
两个线程会同时去调用创建对象的函数,但执行到call_once,后一个进入的线程就会被阻塞
运行结果: