C++设计模式——单例模式

C++系列文章目录

1、C++设计模式——单例模式
2、

文章目录

  • C++系列文章目录
  • 前言
  • 一、饿汉模式
  • 二、懒汉模式
    • 1.简易版
    • 2.局部静态变量保证线程安全(c++11新标准)
    • 3.使用静态类析构
    • 4.使用智能指针析构
  • 总结


前言

一个类中只产生一个对象,并提供一个外部访问点,被程序全员共享,简化了在复杂环境下的配置管理,这种模式被成为单例模式。


一、饿汉模式

不管用不用,在程序开始就加载,会导致程序启动慢,且如果有多个单例类对象实例启动顺序不确定

线程安全,一共只生成一个静态对象

饿汉模式虽好,但其存在隐藏的问题,在于非静态对象(函数外的static对象)在不同编译单元中的初始化顺序是未定义的。如果在初始化完成之前调用 getInstance() 方法会返回一个未定义的实例

/*
*	HungerSingleton.h
*/
#pragma once
#ifndef _HUNGERSINGLETON_H_
#define _HUNGERSINGLETON_H_
#include
#include
class HungerSingleton {
private:
	static HungerSingleton instance;	//静态对象
	HungerSingleton();
	~HungerSingleton();
    //防拷贝
	HungerSingleton(const HungerSingleton&) = delete;
	HungerSingleton& operator=(const HungerSingleton&) = delete;
public:
	static HungerSingleton* GetInstance();
	void func(int n)
	{
		std::cout << n << std::endl;
		Sleep(n);
	}
};
#endif // !_HUNGERSINGLETON_H_
/*
*	HungerSingleton.cpp
*/
#include"HungerSingleton.h"
HungerSingleton HungerSingleton::instance;	//类外初始化
HungerSingleton* HungerSingleton::GetInstance() {
	return &instance;
}
HungerSingleton::HungerSingleton() {
	std::cout << "HungerSingleton构造函数" << std::endl;
}
HungerSingleton::~HungerSingleton() {
	std::cout << "HungerSingleton析构函数" << std::endl;
}
#include"HungerSingleton.h"
#include
#include
using namespace std;
int main() {	//HungerSingleton构造函数
	auto hung = HungerSingleton::GetInstance();
	hung->func(5);	//5
	hung->func(3);	//3
	return 0;	//HungerSingleton析构函数
}

运行后:
HungerSingleton构造函数

HungerSingleton析构函数


二、懒汉模式

等到用的的时候程序再创建实例对象

第一次使用实例对象时,创建对象,进程启动无负载。多个单例实例启动顺序自由控制

非线程安全,当多进程同时创建时,会发生争抢,需要加锁和双重检验

无内存管理,会发生内存泄漏

1.简易版

为保证多线程下的线程安全,使用双重检验,对单例对象加锁保证竞争不会重复创建

/*
*	LazySingleton.h
*/
#pragma once
#ifndef _LAZYSINGLETON_H_
#define _LAZYSINGLETON_H_
#include
#include
class LazySingleton {
private:
	static LazySingleton* instance;	//指向对象的指针
	LazySingleton();
	~LazySingleton();
	static std::mutex m_mtx; //互斥锁
public:
	static LazySingleton* GetInstance();
};
#endif // !_LAZYSINGLETON_H_
/*
*	LazySingleton.cpp
*/
#include"LazySingleton.h"
LazySingleton* LazySingleton::instance = nullptr;	//初始化为空指针
std::mutex LazySingleton::m_mtx;
LazySingleton::LazySingleton() {
	std::cout << "LazySingleton构造函数" << std::endl;
}
LazySingleton::~LazySingleton() {
	std::cout << "LazySingleton析构函数" << std::endl;
}
LazySingleton* LazySingleton::GetInstance() {
	if (instance == nullptr) {
		//加锁
		std::lock_guard<std::mutex>lock(m_mtx);	//类模板,只提供构造函数和析构函数,在析构时自动解锁,可以使用{}来限制使用范围
		if (instance == nullptr) {	//双重检验
			instance = new LazySingleton();
		}
	}
	return instance;
}
#include"LazySingleton.h"
#include
#include
using namespace std;
void func(int n)
{
	cout << LazySingleton::GetInstance() << endl;
}

int main() {
	thread t1(func, 10);
	thread t2(func, 10);
	t1.join();	//进程1运行:创建LazySingleton	单例对象地址不变
	t2.join();	//进程2运行:不用创建
	cout << LazySingleton::GetInstance() << endl;	//不用创建
	cout << LazySingleton::GetInstance() << endl;	//不用创建
	//懒汉模式不会自动析构
	return 0;
}

运行后:
LazySingleton构造函数

结束时不会自动调用析构函数

2.局部静态变量保证线程安全(c++11新标准)

在11新标准中,规定静态变量的创建符合线程安全
1.变量在代码第一次执行到变量声明的地方时初始化。
2.初始化过程中发生异常的话视为未完成初始化,未完成初始化的话,需要下次有代码执行到相同位置时再次初始化。
3.在当前线程执行到需要初始化变量的地方时,如果有其他线程正在初始化该变量,则阻塞当前线程,直到初始化完成为止。
4.如果初始化过程中发生了对初始化的递归调用,则视为未定义行为。

详细讲解参见博客,大佬讲的很详细了

//c++11中局部静态变量的生命周期从第一次创建到程序结束
#pragma once
#ifndef _SINGLETON_H_
#define _SINGLETON_H_
#include
class Singleton {
public:
	//局部静态特性的方式实现单例。
	//静态局部变量只在当前函数内有效,其他函数无法访问。
	//静态局部变量只在第一次被调用的时候初始化,也存储在静态存储区,生命周期从第一次被初始化起至程序结束止。
	static Singleton& GetInstance() {
		static Singleton instan;
		return instan;
	}
	void Print() {
		std::cout << "单例地址:" << this << std::endl;
	}
private:
	Singleton() {
		std::cout << "构造" << std::endl;
	}
};
#endif // !_SINGLETON_H_
#include"Singleton.h"
#include
#include
using namespace std;

void func(int n)
{
	//cout << LazySingleton::GetInstance() << endl;
	Singleton::GetInstance().Print();
}

int main() {
	Singleton::GetInstance().Print();	//在单线程下先创建静态变量,防止多线程竞争
	/*
	 *其实不必要提前使用GetInstance()创建静态变量,
	 *c++11中对静态变量的创建已经是线程安全的了,
	 *在多线程竞争的情况下,若有线程已经开始初始化静态变量,其余线程就会阻塞以保证线程安全。
	 */
	thread t1(func, 10);
	thread t2(func, 10);
	t1.join();
	t2.join();
	return 0;
}

运行后:
构造
单例地址:0046643C
单例地址:0046643C
结束时也不会自动调用析构函数

3.使用静态类析构

使用内嵌静态垃圾回收类实现单例的析构
在懒汉类中加入GC垃圾回收类,该类只有一个公有析构函数,在此析构函数中析构懒汉类的静态变量成员,需要在类外声明其静态对象
当主函数结束时,调用GC垃圾回收类静态对象的析构函数,释放资源

/*
*	LazySingleton.h
*/
#pragma once
#ifndef _LAZYSINGLETON_H_
#define _LAZYSINGLETON_H_
#include
#include
class LazySingleton {
private:
	static LazySingleton* instance;
	LazySingleton();
	~LazySingleton();
	static std::mutex m_mtx; //互斥锁
	class GCarba {		//GC垃圾回收类,该类为私有成员
		public:		//其析构函数设为公有
			~GCarba() {
				std::cout << "GCarba析构" << std::endl;
				if (instance != nullptr) {	//若静态对象不为空,显示调用其析构函数
					delete instance;
				}
			}
	};
	static GCarba garba;
public:
	static LazySingleton* GetInstance();
};
#endif // !_LAZYSINGLETON_H_

/*
*	LazySingleton.cpp
*	多加一行
*/
LazySingleton::GCarba LazySingleton::garba;		//声明GC垃圾回收类静态对象

运行后:
LazySingleton构造函数

GCarba析构
LazySingleton析构函数

4.使用智能指针析构

GetInstance()函数返回智能指针时,创建了临时对象,导致引用计数又加了1
销毁采用局部静态类进行销毁,析构函数要声明为公有,否则智能指针析构时无法访问

/*
*	LazySingleton.h
*/
#pragma once
#ifndef _LAZYSINGLETON_H_
#define _LAZYSINGLETON_H_
#include
#include
class LazySingleton {
private:
	//static LazySingleton* instance;
	static std::shared_ptr<LazySingleton> instance;
	/**/
	LazySingleton();
	//~LazySingleton();
	LazySingleton(LazySingleton const&) = delete;
	LazySingleton& operator=(LazySingleton const&) = delete;
	static std::mutex m_mtx; //互斥锁
	/**/
	class GCarba {
		public:
			~GCarba() {
				std::cout << "GCarba析构" << std::endl;
				if (instance != nullptr) {	//若静态对象不为空,显示调用其析构函数
					//std::cout << instance.use_count() << std::endl;
					instance.reset();
					//instance.reset();
					//std::cout << instance.use_count() << std::endl;		//静态类析构调用智能指针析构,引用计数变0
					std::cout << "智能指针析构" << std::endl;
				}
			}
	};
	static GCarba garba;
public:
	static std::shared_ptr<LazySingleton> GetInstance();
	~LazySingleton();
};
#endif // !_LAZYSINGLETON_H_
/*
*	LazySingleton.cpp
*/
#include"LazySingleton.h"
std::shared_ptr<LazySingleton> LazySingleton::instance;
std::mutex LazySingleton::m_mtx;
LazySingleton::GCarba LazySingleton::garba;
LazySingleton::LazySingleton() {
	std::cout << "LazySingleton构造函数" << std::endl;
}
/**/
LazySingleton::~LazySingleton() {
	std::cout << "LazySingleton析构函数" << std::endl;
}

std::shared_ptr<LazySingleton> LazySingleton::GetInstance() {
	if (instance == nullptr) {
		//加锁
		std::lock_guard<std::mutex>lock(m_mtx);
		if (instance == nullptr) {
			std::cout << instance.use_count() << std::endl;		//还没智能指针置空,引用计数为0
			instance.reset(new LazySingleton());
			//instance = std::make_shared(new LazySingleton());
			std::cout << instance.use_count() << std::endl;		//智能指针指向新对象,引用计数为1
		}
	}
	return instance;	//返回一个智能指针,引用计数为2
}
/*
*	main
*/
cout << LazySingleton::GetInstance().use_count() << endl;	// 2
cout << LazySingleton::GetInstance() << endl;
cout << LazySingleton::GetInstance().use_count() << endl;	// 2
cout << LazySingleton::GetInstance().use_count() << endl;	// 2
return 0;		//先析构静态GC类,在其中析构instance智能指针。在类内instance引用计数为1,
/*
*	使用LazySingleton::GetInstance().use_count()中GetInstance()返回智能指针,创建了临时对象,导致引用计数变为2。
*/

运行后:
LazySingleton构造函数

GCarba析构
LazySingleton析构函数
智能指针析构


总结

熟悉了几种单例模式的实现方法,了解了如何保证线程安全以及如何自动调用析构函数的方法,对于智能指针有了初步的了解。

你可能感兴趣的:(C++,c++,设计模式,单例模式)