
文章目录
- 设计模式
- 前言
- 一、单例模式
- 1.懒汉模式
- 特点:
- 用“静态指针 + 用到时初始化”实现
- 运行结果:
- 缺点
- 改进
- 线程安全版本:
- 还可以用局部静态变量实现懒汉模式
- 例子
- 2.饿汉模式
- 优点:
- 缺点:
- 使用条件:
前言
常见的设计模式。
一、单例模式
定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。
即需要保证:该类不能被复制,不能被公开创造。
对于C++,就是它构造函数,拷贝构造函数,赋值函数都不能被公开调用,即private,最好禁用拷贝构造函数,赋值函数。
懒汉模式的特点是延迟加载,比如配置文件,采用懒汉式的方法,配置文件的实例直到用到的时候才会加载,不到万不得已就不会去实例化类,也就是说在第一次用到类实例的时候才会去实例化。
//懒汉
//代码实例(线程不安全)
#include
using namespace std;
class Singleton{
private:
Singleton(){
//构造函数设置为private,防止直接构造
m_count ++;
printf("Singleton begin\n");
printf("Singleton end\n");
}
Singleton(const Singleton& st)=delete;//禁用拷贝构造
Singleton& operator =(const Singleton& st)=delete;//禁用赋值
static Singleton *single;//定义一个唯一指向实例的指针,并且是私有的
public:
static Singleton *GetInstance();//定义一个公有函数,可以获取这个唯一实例
static void print();
static int m_count;
};
//将唯一指向实例的指针初始化为nullptr
Singleton *Singleton::single = nullptr;
int Singleton::m_count = 0;
Singleton *Singleton::GetInstance(){
if(single == nullptr){//判断是不是第一次使用
single = new Singleton;
}
return single;
}
void Singleton::print(){
cout<<"总共"<<m_count<<"个例程"<<endl;
}
int main()
{
cout<<"开始"<<endl;
Singleton* a1 = Singleton::GetInstance();
cout << a1 << endl;
a1->print();
Singleton* a2 = Singleton::GetInstance();
cout << a2 << endl;
a2->print();
system("pause");
return 0;
}
运行结果:
在初始化为null的时候并没有创建例程,而在之后调用GetInstance()函数时,才创建例程;第二次调用时与第一次调用时返回的是同一个例程。
还存在内存泄漏的问题,new出来的东西始终没有释放。
改进
#include
using namespace std;
class Singleton{
private:
class Delete {
public:
~Delete(){
if(Singleton::single){
cout<<"析构单例程"<<endl;
delete Singleton::single;
}
}
};
static Delete _delete;
Singleton(const Singleton& st)=delete;//禁用拷贝构造
Singleton& operator =(const Singleton& st)=delete;//禁用赋值
Singleton(){
//构造函数设置为private,防止直接构造
m_count ++;
printf("Singleton begin\n");
printf("Singleton end\n");
}
static Singleton *single;//定义一个唯一指向实例的指针,并且是私有的
public:
static Singleton *GetInstance();//定义一个公有函数,可以获取这个唯一实例
static void print();
static int m_count;
};
//将唯一指向实例的指针初始化为nullptr
Singleton *Singleton::single = nullptr;
Singleton::Deletor Singleton::_delete;
int Singleton::m_count = 0;
Singleton *Singleton::GetInstance(){
if(single == nullptr){//判断是不是第一次使用
single = new Singleton;
}
return single;
}
void Singleton::print(){
cout<<"总共"<<m_count<<"个例程"<<endl;
}
int main()
{
cout<<"开始"<<endl;
Singleton* a1 = Singleton::GetInstance();
cout << a1 << endl;
a1->print();
Singleton* a2 = Singleton::GetInstance();
cout << a2 << endl;
a2->print();
system("pause");
return 0;
}
这样可以解决内存泄漏的问题
在程序运行结束时,系统会调用Singleton的静态成员Garbo的析构函数,该析构函数会删除单例的唯一实例。
使用这种方法释放单例对象有以下特征:
a. 在单例类内部定义专有的嵌套类;
b. 在单例类内定义私有的专门用于释放的静态成员;
c. 利用程序在结束时析构全局变量的特性,选择最终的释放时机。
线程安全版本:
在判断对象是否为空时需要加锁(可以优化,只有第一次检测对象为空时上锁,其余检测对象不为空直接退出)
using namespace std;
class Singleton{
private:
Singleton(){printf( "Singleton init\n");}
//构造函数设置为private,防止直接构造
~Singleton(){printf( "delete Singleton \n");}
Singleton(const Singleton&) =delete;
Singleton& operator=(const Singleton&) =delete;
private:
class Delete{
public:
~Delete(){
if(Singleton::_instance!=nullptr){
delete Singleton::_instance;
}
}
};
private:
static Singleton* _instance;
static Delete _delete;
static mutex _mtx;
public:
static Singleton* GetInstance()
{
if(_instance==nullptr){ //第一重检测,如果未初始化。
lock_guard<mutex> lck(_mtx); // 上锁,RAII技法,离开if{},自动解锁
if(_instance== nullptr){ //第二重检测,还未初始化,new
_instance = new Singleton();
}
}
return _instance;
}
};
Singleton* Singleton::_instance =nullptr;
Singleton::Delete Singleton::_delete;
mutex Singleton::_mtx;
还可以用局部静态变量实现懒汉模式
//代码实例(线程不安全)
template<typename T>
class Singleton
{
public:
static T& getInstance()
{
static T instance;
return instance;
}
private:
Singleton(){};
Singleton(const Singleton&)=delete;
Singleton& operator=(const Singleton&)=delete;
};
静态局部变量的实现方式也是线程不安全的。
如果存在多个单例对象的析构顺序有依赖时,可能会出现程序崩溃的危险。
对于局部静态对象的也是一样的。
因为 static T instance;语句不是一个原子 *** 作,在第一次被调用时会调用Singleton的构造函数,而如果构造函数里如果有多条初始化语句,则初始化动作可以分解为多步 *** 作,就存在多线程竞争的问题。
为什么存在多个单例对象的析构顺序有依赖时,可能会出现程序崩溃的危险?
原因:由于静态成员是在第一次调用函数GetInstance时进行初始化,调用构造函数的,因此构造函数的调用顺序时可以唯一确定了。
对于析构函数,我们只知道其调用顺序和构造函数的调用顺序相反,但是如果几个Singleton类的析构函数之间也有依赖关系,而且出现类似单例实例A的析构函数中使用了单例实例B,但是程序析构时是先调用实例B的析构函数,此时在A析构函数中使用B时就可能会崩溃。
#include
#include
using namespace std;
class Log
{
public:
static Log* GetInstance()
{
static Log oLog;
return &oLog;
}
void Output(string strLog)
{
cout<<strLog<<(*m_pInt)<<endl;
}
private:
Log():m_pInt(new int(3))
{
}
~Log()
{cout<<"~Log"<<endl;
delete m_pInt;
m_pInt = NULL;
}
int* m_pInt;
};
class Context
{
public:
static Context* GetInstance()
{
static Context oContext;
return &oContext;
}
~Context()
{
Log::GetInstance()->Output(__FUNCTION__);
}
void fun()
{
Log::GetInstance()->Output(__FUNCTION__);
}
private:
Context(){}
Context(const Context& context);
};
int main(int argc, char* argv[])
{
Context::GetInstance()->fun();
return 0;
}
在这个反例中有两个Singleton: Log和Context,Context的fun和析构函数会调用Log来输出一些信息,结果程序Crash掉了,该程序的运行的序列图如下(其中画红框的部分是出问题的部分):
解决方案:对于析构的顺序,我们可以用一个容器来管理它,根据单例之间的依赖关系释放实例,对所有的实例的析构顺序进行排序,之后调用各个单例实例的析构方法,如果出现了循环依赖关系,就给出异常,并输出循环依赖环。
单例类定义的时候就进行实例化。
因为main函数执行之前,全局作用域的类成员静态变量m_Instance已经初始化,故没有多线程的问题。
//代码实例(线程安全) //.h文件
class Singleton {
public:
static Singleton& GetInstance();
private:
Singleton(){}
Singleton(const Singleton&);
Singleton& operator= (const Singleton&);
private:
static Singleton m_Instance;
}; //CPP文件
Singleton Singleton::m_Instance;//类外定义-不要忘记写
Singleton& Singleton::GetInstance() {
return m_Instance;
} //函数调用
Singleton& instance = Singleton::GetInstance();
优点:
实现简单,多线程安全。
缺点:
a. 如果存在多个单例对象且这几个单例对象相互依赖,可能会出现程序崩溃的危险。
原因:对编译器来说,静态成员变量的初始化顺序和析构顺序是一个未定义的行为;具体分析在懒汉模式中也讲到了。
b. 在程序开始时,就创建类的实例,如果Singleton对象产生很昂贵,而本身有很少使用,
这种方式单从资源利用效率的角度来讲,比懒汉式单例类稍差些。
但从反应时间角度来讲,则比懒汉式单例类稍好些。
使用条件:
a. 当肯定不会有构造和析构依赖关系的情况。
b. 想避免频繁加锁时的性能消耗
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)