C++单例模式

发布于:2025-05-25 ⋅ 阅读:(18) ⋅ 点赞:(0)

单例模式

一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。

1.饿汉模式

提前(main函数启动时)把实例对象创建好,随时准备用
那什么对象在main函数之前创建好,全局对象

class A
{
public:
	static A* Getinstance()
	{
		return &_init;
	}
	void Add(const string&key,const string&value)
	{
		_mp[key] = value;
	}
	void Print()
	{
		for (auto& e : _mp)
		{
			cout << e.first << " " << e.second << endl;
		}
	}
	A(A& a) = delete;
	A& operator=(A& a) = delete;
private:
	A()
	{

	}
	map<string, string> _mp;
	int _n = 0;
	static A _init;
};
A A::_init;

1.首先我们要知道创建唯一一份对象那么这个对象的构造函数是要被禁用的
防止创建出多个对象
2.但是如果我们单单只创建在类外面的全局对象是无法访问类里面的私有成员的,所以我们要在类里面创建,但是类里面又不能直接创建,因为还没有A这个类呢怎么可以就初始化了呢,所以我们要定义成静态的。
3.定义成静态的就是它是静态区的(类里面声明类外面定义),但是它又是要突破类域才能访问的,而且它在你的类域里面可以访问私有和公有的成员变量和成员函数
4.但是如果单单是这样的话我们没法提防拷贝和赋值,所以我们还要将拷贝和赋值给禁用掉

int main()
{
	A::Getinstance()->Add("left","左边");
	A::Getinstance()->Add("right","右边");
	A::Getinstance()->Add("middle","中间");
	A::Getinstance()->Print();
	return 0;
}

它都是调用一个对象去实现这些操作的
优点:
实现简单
缺点:
可能会导致进程启动慢、如果两个单例有先后顺序,那么饿汉无法控制(如果有两个单例A和B,A包含了B的一些信息,但是你全局对象又不知道什么时候创建,你A先创建B还没有创建好就会出问题了)

2.懒汉模式

第一次用的时候再创建(现吃现做)

    class B
{
public:
	static B* Getinstance()
	{
		if (_initb == nullptr)
		{
			_initb = new B;
		}
		return _initb;
	}
	void Add(const string& key, const string& value)
	{
		_mp[key] = value;
	}
	void Print()
	{
		for (auto& e : _mp)
		{
			cout << e.first << " " << e.second << endl;
		}
	}
   B(const B& b) = delete;
B& operator=(const B& b) = delete;
	~B()
	{

	}
private:
	B()
	{

	}

	map<string, string> _mp;
	int _n = 0;
	static B* _initb;
};
B* B::_initb=nullptr;
int main()
{
	B::Getinstance()->Add("left","左边");
	B::Getinstance()->Add("right","右边");
	B::Getinstance()->Add("middle","中间");
	B::Getinstance()->Print();
	delete B::Getinstance();
	return 0;
}

1.我们采用指针的形式,等到第一次使用的时候再去new这个对象
2.但是我们在平时写项目的时候期望在析构的时候持久化,把数据写到文件里面但是我们上面写的这种形式确实删除了但是不好,因为它防不住在删除之后又创建出这个单例对象再去给它一些数值,所以我们期望的是它在main函数结束之后自动调用

class B
{
public:
	static B* Getinstance()
	{
		if (_initb == nullptr)
		{
			_initb = new B;
		}
		return _initb;
	}
	void Add(const string& key, const string& value)
	{
		_mp[key] = value;
	}
	void Print()
	{
		for (auto& e : _mp)
		{
			cout << e.first << " " << e.second << endl;
		}
	}
	static void DeleteInstance()
	{
		if (_initb)
		{
			delete _initb;
			_initb = nullptr;
		}
	}
	B(const B& b) = delete;
B& operator=(const B& b) = delete;
	
private:
	B()
	{

	}
	~B()
	{

	}
	map<string, string> _mp;
	int _n = 0;
	static B* _initb;
	class gc
	{
	public:
		~gc()
		{
			DeleteInstance();
		}
	};
	static gc _gc;
};
B::gc B::_gc;
B* B::_initb=nullptr;

就是有点像智能指针,把我们的对象交给一个类去管理,我们提供了一个Getinstance你可以去手动释放,也可以交给我们这个类去在main函数结束之后自动调用main函数释放。
3.但是这个类其实是存在线程安全问题的,就是你两个线程同时进来就会创建两个类,所以我们就要给它加一个锁

2.1懒汉模式的线程安全

class B
{
public:
	static B* Getinstance()
	{
		if (_initb == nullptr)
		{
			unique_lock<mutex> lock(_mte);
			if (_initb == nullptr)
			{
				_initb = new B;
			}
		}
	
		return _initb;
	}
	void Add(const string& key, const string& value)
	{
		_mp[key] = value;
	}
	void Print()
	{
		for (auto& e : _mp)
		{
			cout << e.first << " " << e.second << endl;
		}
	}
	static void DeleteInstance()
	{
		if (_initb)
		{
			delete _initb;
			_initb = nullptr;
		}
	}
	B(const B& b) = delete;
B& operator=(const B& b) = delete;
	
private:
	B()
	{

	}
	~B()
	{

	}
	map<string, string> _mp;
	int _n = 0;
	static B* _initb;
	static mutex _mte;
	class gc
	{
	public:
		~gc()
		{
			DeleteInstance();
			cout << "删除成功" << endl;
		}
	};
	static gc _gc;
};
B::gc B::_gc;
B* B::_initb=nullptr;

在这里插入图片描述
我们来剖析一下这个代码
1.首先这个锁为什么不放在第二个if里面呢
因为比如说你有两个线程t1,t2,它们在进入完这个if语句以后,一个线程拿到锁去初始化对象,下一个继续拿锁因为它在判断之后它又可以去创建出这个对象
2.其次是这个对象只会创建一次,所以我们在创建完之后其实就不用走判断那个流程了,连锁也不要判断了,所以我们在创建锁之前再加一个判断

2.2简单方法创建懒汉模式

class B
{
public:
	static B& Getinstance()
	{
		static B initb;
		return initb;
	}
	B(B& b) = delete;
	B& operator=(B& b) = delete;

private:
	B()
	{
		++x;
	}
	int x = 0;
	B(const B& b) = delete;
    B& operator=(const B& b) = delete;
};

局部的静态对象,在第一次调用时初始化
那这个是不是线程安全的呢,就是我们这里有一个x,它会不会被多个线程++呢
C++11之前,它不是,也就是说,C++11之前这个代码不安全的,我们需要通过加锁来实现线程安全
C++11之后可以保证局部静态对象的初始化时线程安全的,只初始化一次


网站公告

今日签到

点亮在社区的每一天
去签到