C++单例模式
🌟🌟hello,各位读者大大们你们好呀🌟🌟
🚀🚀系列专栏:【C++的学习】
📝📝本篇内容:设计模式;单例模式;饿汉模式;懒汉模式
⬆⬆⬆⬆上一篇:特殊类的设计
💖💖作者简介:轩情吖,请多多指教(> •̀֊•́ ) ̖́-
1.设计模式
设计模式(Design Pattern)是一种在软件开发中,特别是在面向对象编程(OOP)中,用于解决常见设计问题的可复用解决方案。它并不是可以直接转换成代码的模板,而是一种设计思路或者经验总结,旨在提高代码的可读性、可维护性、可扩展性和重用性。我们的单例模式就是设计模式之一
2.单例模式
一个类只能创建一个对象,就是单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享
2.1.饿汉模式
单例模式一共分为两种,分别是饿汉模式和懒汉模式,我们先来讲一下饿汉模式,代码如下
cpp
#include <iostream>
#include <vector>
#include <string>
#include <thread>
#include <mutex>
using namespace std;
//饿汉模式
class Singleton
{
public:
//使用static函数来返回能创建的唯一对象
static Singleton* GetInstance()
{
return _instance;
}
//要保证线程安全
void Add(const string& d)
{
mtx.lock();
_data.push_back(d);
mtx.unlock();
}
//读数据也会有线程安全问题
void Print()
{
int cnt = 0;
mtx.lock();
for (auto& e : _data)
{
cnt++;
cout << e << endl;
}
mtx.unlock();
cout << "cnt=" << cnt << endl;
}
private:
Singleton()//将构造函数隐藏起来,保证不能随意创建对象
{}
//⭐⭐⭐这边要注意,需要把它给禁用掉,否则,可以通过*GetInstance来进行拷贝构造
Singleton(const Singleton& sg) = delete;
Singleton& operator=(const Singleton& sg) = delete;//保险起见也delete
private:
vector<string> _data;
//对象也要是static,这样才能让GetInstance返回
static Singleton* _instance;
mutex mtx;//保证在输入数据时的安全
};
Singleton* Singleton::_instance = new Singleton;
int main()
{
srand((unsigned)time(nullptr));
int n = 30;
thread t1([n] {
for (int i = 0; i < n; i++)
Singleton::GetInstance()->Add("thread t1-" + to_string(rand() % 100 + 1));
});
thread t2([n] {
for (int i = 0; i < n; i++)
Singleton::GetInstance()->Add("thread t2-" + to_string(rand() % 100 + 1));
});
t1.join();
t2.join();
Singleton::GetInstance()->Print();
return 0;
}
在上述代码中,我把相关的重要的点已经以注释的形式展现出来了
优点 :可以发现我们的代码,写的非常简单,其实这个就是饿汉模式的特点之一,同时如果这个单例对象在多线程并发环境下频繁使用,性能要求轻高,那么显然使用饿汉模式来避免资源竞争,提高响应速度更好
缺点 :如果单例对象特别的大,在我们的main前就要进行申请,这样就会造成一个问题,当代码中万一不需要使用,它会占用资源,程序启动的速度会受到影响,并且如果有多个单例类对象实例启动顺序不确定,懒汉可以做到那接下来我们讲一下懒汉
2.2.懒汉模式
cpp
#include <iostream>
#include <vector>
#include <string>
#include <thread>
#include <mutex>
#include <ctime>
#include <cstdlib>
using namespace std;
//懒汉模式
class Singleton
{
public:
static Singleton* GetInstance()
{
if (_instance == nullptr)//双重检测
{
//这边加上锁后,每次有线程进来时都会上锁,然后解锁,再返回;即使_instance
//不为nullptr,这样效率就会大大降低,因此我们需要可以双重检测
_mtx.lock();
if (_instance == nullptr)
{
_instance = new Singleton;
}
_mtx.unlock();
}
return _instance;
}
//要注意线程安全
void Add(const string& str)
{
_mtx.lock();
_data.push_back(str);
_mtx.unlock();
}
//要注意线程安全
void Print()
{
int cnt = 0;//计算数量
_mtx.lock();
for (auto& e : _data)
{
cnt++;
cout << e << endl;
}
_mtx.unlock();
cout <<"cnt="<< cnt << endl;
}
~Singleton()
{
//析构函数可以用来做一些最后的写入工作
}
//garbage collection--垃圾回收
struct GC
{
~GC()
{
DelInstance();
}
};
//用来回收释放单例,必须使用static,这样才能保证在程序结束的时候,能够调用_gc的析构
//不使用static的话,就是作为单例对象一部分,而单例还需要等待释放,而GC就是为了释放单例,就出现了蛋生鸡鸡生蛋问题
static GC _gc;
static void DelInstance()//这边也要保证是static,不然回收类无法使用
{
_mtx.lock();//这个也要保证线程安全
if (_instance)
{
delete _instance;
_instance = nullptr;//防止多次调用,而程序崩溃,设为nullptr后,只能delete一次
}
_mtx.unlock();
}
private:
Singleton()
{}
//⭐⭐⭐这边要注意,需要把它给禁用掉,否则,可以通过*GetInstance来进行拷贝构造
Singleton(const Singleton& sg) = delete;
Singleton& operator=(const Singleton& sg) = delete;//保险起见也delete
private:
static Singleton* _instance;//用来接受创建的唯一对象
vector<string> _data;//存数据
//也得设为static,不然GetInstance中无法使用
static mutex _mtx;
};
//懒汉是在使用时才创建,所以说这边先初始化为nullptr
Singleton* Singleton::_instance = nullptr;
mutex Singleton::_mtx;//表示初始化锁
int main()
{
srand((unsigned)time(nullptr));
int n = 30;
thread t1([n] {
for (int i = 0; i < n; i++)
Singleton::GetInstance()->Add("thread t1-" + to_string(rand() % 100 + 1));
});
thread t2([n] {
for (int i = 0; i < n; i++)
Singleton::GetInstance()->Add("thread t2-" + to_string(rand() % 100 + 1));
});
t1.join();
t2.join();
Singleton::GetInstance()->Print();
return 0;
}
具体代码如上,我们可以清楚的看到它比饿汉模式更复杂,但是它也不是一无是处,还是有优点的
优点 :第一次使用实例对象时才会创建对象,而不像饿汉模式是在main前创建,因此进程启动无负载;多个单例实例启动顺序自由控制
缺点 :比较复杂,其中设计double-check的方式加锁来保证效率和线程安全;并且内嵌垃圾回收类一般全局都要使用单例对象,所以单例对象一般不需要显示释放
在这份代码中,其实很多细节,也能够加深对类的理解,比如我们类中定义了静态成员,其实我们可以把它们看成普通的静态变量,它们都是在程序结束时释放或者调用析构,因此我们的懒汉模式中_gc对象就是在程序结束时析构释放的,同时它调用了DelInstance函数,在DelIstance函数中释放了单例对象。
还有一个细节,就是我们的锁,对于它的构造函数是无参的,但是我们还是需要在类外进行初始化,不然相等于没有这个变量,因为类中只是声明
同样的还有一个点不知道大家有没有疑惑,那就是明明Singleton类的构造函数已经private了,我们的static单例指针还能调用new来构造?不要忘了这个static单例指针也是类中的,它只是在类外初始化罢了
🌸🌸C++单例模式的知识大概就讲到这里啦,博主后续会继续更新更多C++的相关知识,干货满满,如果觉得博主写的还不错的话,希望各位小伙伴不要吝啬手中的三连哦!你们的支持是博主坚持创作的动力!💪💪