【设计模式】

【设计模式】,第1张

设计模式

文章目录
  • 设计模式
  • 前言
  • 一、单例模式
    • 1.懒汉模式
      • 特点:
      • 用“静态指针 + 用到时初始化”实现
      • 运行结果:
      • 缺点
      • 改进
      • 线程安全版本:
      • 还可以用局部静态变量实现懒汉模式
      • 例子
    • 2.饿汉模式
      • 优点:
      • 缺点:
      • 使用条件:


前言

常见的设计模式。


一、单例模式

定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。


即需要保证:该类不能被复制,不能被公开创造。


对于C++,就是它构造函数,拷贝构造函数,赋值函数都不能被公开调用,即private,最好禁用拷贝构造函数,赋值函数。

1.懒汉模式 特点:

懒汉模式的特点是延迟加载,比如配置文件,采用懒汉式的方法,配置文件的实例直到用到的时候才会加载,不到万不得已就不会去实例化类,也就是说在第一次用到类实例的时候才会去实例化。

用“静态指针 + 用到时初始化”实现
//懒汉
//代码实例(线程不安全) 
#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掉了,该程序的运行的序列图如下(其中画红框的部分是出问题的部分):

解决方案:对于析构的顺序,我们可以用一个容器来管理它,根据单例之间的依赖关系释放实例,对所有的实例的析构顺序进行排序,之后调用各个单例实例的析构方法,如果出现了循环依赖关系,就给出异常,并输出循环依赖环。

2.饿汉模式

单例类定义的时候就进行实例化。

因为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. 想避免频繁加锁时的性能消耗

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/langs/673405.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-04-19
下一篇2022-04-19

发表评论

登录后才能评论

评论列表(0条)

    保存