单例模式
什么是单例模式
单例模式是设计模式的一种
饿汉模式
#include <iostream>
#include<map>
#include <string>
using namespace std;
//单例模式的特点:全局只有一个对象!
class Singleton
{
public:
//.....
private:
map<string,int> _info;
};
//我们要求这个类全局只有唯一一份,而且易于访问!
我们应该怎么进行设计呢?——设计成全局变量?如果是全局变量有一个我们就可以有第二个第三个!——那么我们应该如何保证只能有一个对象呢?
- 首先构造函数私有化!
#include <iostream>
#include<map>
#include <string>
using namespace std;
//单例模式的特点:全局只有一个对象!
class Singleton
{
public:
private:
Singleton()//首先我们就要封死构造函数!
{}
private:
map<string,int> _info;
};
int main()
{
//如果不封死构造函数我们就可以随意的创建对象了
Singleton info;
Singleton info2;
Singleton info3;
return 0;
}
- 创建一个实例化的全局对象和获取这个实例化全局对象的接口!
#include <string>
using namespace std;
//单例模式的特点:全局只有一个对象!
class Singleton
{
public:
static Singleton& GetInstance()//一般单例会提供一个静态函数接口用来获取实例化对象!
{
//那么我们该如何创建一个全局的实例化对象呢?
//首先肯定不是创建全局对象因为构造函数被私有化无法在外面调用!
//我们可以创建一个静态的成员变量,这个静态的生命周期就是全局的!
return _sins;
//然后返回这个对象就可以了!
}
private:
Singleton()
{}
Singleton(const Singleton& ) = delete;
Singleton operator=(const Singleton&) = delete;
private:
map<string,int> _info;
private:
static Singleton _sins;//创建静态的成员变了
};
Singleton Singleton::_sins;
int main()
{
Singleton& sins = Singleton::GetInstance();//我们就可以获取到这个对象了!
return 0;
}
- 禁止拷贝构造和赋值重装
#include <string>
using namespace std;
//单例模式的特点:全局只有一个对象!
class Singleton
{
public:
static Singleton& GetInstance()//一般单例会提供一个静态函数接口用来获取实例化对象!
{
//那么我们该如何创建一个全局的实例化对象呢?
//首先肯定不是创建全局对象因为构造函数被私有化无法在外面调用!
//我们可以创建一个静态的成员变量,这个静态的生命周期就是全局的!
return _sins;
//然后返回这个对象就可以了!
}
private:
Singleton()
{}
Singleton(const Singleton& ) = delete;
Singleton operator=(const Singleton&) = delete;
private:
map<string,int> _info;
private:
static Singleton _sins;//创建静态的成员变了
};
Singleton Singleton::_sins;
int main()
{
Singleton& sins = Singleton::GetInstance();//我们就可以获取到这个对象了!
//如果我们不禁用哦我们就可以
Singleton copy = Singleton::GetInstance();//调用了拷贝构造
Singleton copy2 = copy;//调用了operator=
return 0;
}
- 如果我们想要修改这个变量一般都会提供一些其他的修改接口!而不是直接修改!
#include <iostream>
#include<map>
#include <string>
using namespace std;
//单例模式的特点:全局只有一个对象!
class Singleton
{
public:
Singleton& GetInstance()//一般单例会提供一个这个函数接口用来获取实例化1对象!
{
return _sins;
}
//提供一个插入接口
void Insert(string name,int salary)
{
_info[name] = salary;
}
void print()//提供一个打印接口
{
for(auto kv:_info)
{
cout << kv.first << ":" << kv.second <<endl;
}
}
private:
Singleton()
{}
Singleton(const Singleton& ) = delete;
Singleton operator=(const Singleton&) = delete;
private:
map<string,int> _info;
private:
static Singleton _sins;//我们可以创建一个静态的成员函数,这个静态的生命周期就是全局的!
};
Singleton Singleton::_sins;
int main()
{
//有两种调用方法!
Singleton& sins = Singleton::GetInstance();
sins.Insert("张三",1000);
Singleton::GetInstance().Insert("李四",1000000);
sins.print();
return 0;
}
==这就是饿汉模式——意思就是一开始就创建对象!在main函数之前(就像是饿汉一样,等不及了直接创建对象)==
饿汉模式的缺点
- 如果我们单例对象的一开始要初始化的数据太多了那么就会导致启动缓慢!
==因为在main函数之前就要进行了初始化!==
懒汉模式
为了解决饿汉模式的缺点所以又设计了一种新的模式——懒汉模式!
==懒汉模式不会一开始就创建对象!那么是怎么实现的呢?其实也很简单==
#include <iostream>
#include<map>
#include <string>
using namespace std;
//单例模式的特点:全局只有一个对象!
class Singleton
{
public:
static Singleton& GetInstance()
{
//第一次获取单例对象的时候进行创建!
if(_psins == nullptr)
{
new Singleton;
}
return *_psins;
}
private:
Singleton()
{}
Singleton(const Singleton& ) = delete;
Singleton operator=(const Singleton&) = delete;
private:
map<string,int> _info;
private:
static Singleton* _psins;//将这个修改成指针就可以了!
};
Singleton* Singleton::_psins = nullptr;
int main()
{
Singleton& sins = Singleton::GetInstance();//这时候,对象是在这里创建的!
return 0;
}
懒汉模式的缺点
==懒汉模式有线程安全的问题!==
当多个线程一起调用同一个单例对象的时候,存在线程安全的风险!
==解决方式很简单——加锁==
#include <iostream>
#include<map>
#include <string>
#include <mutex>
using namespace std;
//单例模式的特点:全局只有一个对象!
class Singleton
{
public:
static Singleton& GetInstance()
{
//双检查加锁!
if(_psins == nullptr) //对象new出来以后,避免每次都加锁的检查,提供性能
{
_mut.lock();
//因为_mut是一个静态的成员变量所以不用担心在第一次调用的时候_mut不存在!
if (_psins == nullptr) //保证线程安全,只new一次
{
new Singleton;
}
_mut.unlock();
}
//为什么我们要在外面也加上一次判断?
//因为我们只需要在第一次的时候进行加锁,因为只有第一次会new后面就不会了!
//如果我们不在外面多加一次判断,那么每一次都会有加锁解锁的消耗!会导致性能的消耗!
//有了外面的一次判断在第二次进来的时候,就不会进行加锁与解锁了!
return *_psins;
}
//.....
private:
Singleton()
{}
Singleton(const Singleton& ) = delete;
Singleton operator=(const Singleton&) = delete;
private:
map<string,int> _info;
private:
static Singleton* _psins;
static mutex _mut;//因为静态成员函数里面没有this指针,所以我们也只能使用静态的成员变量
};
Singleton* Singleton::_psins = nullptr;
mutex Singleton::_mut;//类外面初始化
==这样子就线程安全了!——只有一个线程可以进入锁里面!==
懒汉对象的回收
==一般单例对象可以不考虑释放!因为单例对象是一个全局的对象!在整个程序运行期间都需要使用到这个单例对象!所以我们一般都不用考虑释放!当整个程序结束后,OS会自己回收这些资源!==
==单例对象不用的时候,我们就必须手动处理,一些资源需要进行保存——所以我们可以提供一些函数==
class Singleton
{
public:
static void DELInstance()//可以使用这个函数来进行手动处理
{
//保存数据到文件
//...
std::lock_guard<mutex> lk(_mut);
if(_psins)
{
delete _psins;
_psins = nullptr;
}
}
private:
//....
};
==但是为了防止我们自己忘记了所以我们可以怎么做==
class Singleton
{
public:
//....
static void DELInstance()
{
//保存数据到文件
//...
std::lock_guard<mutex> lk(_mut);
if(_psins)
{
delete _psins;
_psins = nullptr;
}
}
class GC
{
public:
~GC()//内部类是外部类的天然友元
{
if(_psins)//如果没有手动释放,那么就帮忙释放
{
DELInstance();
}
}
};
private:
//.....
private:
map<string,int> _info;
private:
static Singleton* _psins;
static mutex _mut;
static GC _gc;
};
Singleton* Singleton::_psins = nullptr;
mutex Singleton::_mut;
Singleton::GC Singleton::_gc;
//这种写法我们既可以手动回收,也可以程序调用的时候自动让它释放!
==我们可以在里面设计一个GC的类!写一个静态的成员变量_gc,当我们忘记的调用DELInstance的时候,一旦程序结束, _gc的生命周期结束就会自动的调用析构函数去调用DELInstance==
懒汉模式的写法(2)
class Singleton
{
public:
static Singleton& GetInstance()
{
//我们只要怎么写就可以了!
static Singleton sins;
return sins;
}
void Insert(string name,int salary)
{
_info[name] = salary;
}
void print()
{
for(auto kv:_info)
{
cout << kv.first << ":" << kv.second <<endl;
}
}
private:
Singleton()
{
cout <<"Singleton()" << endl;
}
Singleton(const Singleton& ) = delete;
Singleton operator=(const Singleton&) = delete;
private:
map<string,int> _info;
};
int main()
{
Singleton& sins = Singleton::GetInstance();
sins.Insert("张三",1000);
Singleton::GetInstance().Insert("李四",1000000);
sins.print();
return 0;
}
==懒汉就是在第一次调用的时候初始化!——因为它是一个局部的静态!所以是在main函数之后进行初始化!==
==我们可以看到下面这个代码只初始化了一次!==
static Singleton sins;
所以这也是一个单例模式!