理解 C++单例模式

文章目录

  • C++实现单例模式
    • 1. 单例模式
    • 2. 懒汉式
      • 2.1 懒汉式——普通实现
      • 2.2 懒汉式——处理线程安全
      • 2.3 懒汉式——优化性能
      • 2.4 懒汉式——嵌套类实现
    • 2. 饿汉式
      • 2.1 饿汉式实现
    • 3. 总结

C++实现单例模式

1. 单例模式

单例模式,目的是确保一个类仅有一个实例化对象, 该实例被所有程序模块共享(提供一个全局访问点)
例如我们最常用的 腾讯视频, 计算器, 有道云笔记等软件都是默认单例模式, Windows的任务管理器和垃圾站都是典型的单例;
往小处说, 我们在做日志系统时, 我们只需要做一个全局日志读写对象, 不必要每次 new再 delete

单例模式的应用场景:

  • 在一些需要大量消耗资源的情况下, 可以考用单例模式来减小维护开销;像任务管理器不断更新进程信息, 再比如数据库的连接, 都是比较耗资源的

定义一个单例类:

  • 私有化构造函数, 防止外部调用创建
  • 使用类的私有静态指针变量指向唯一实例
  • 使用公有静态方法获取该实例

2. 懒汉式

懒汉式: 第一次使用的时候才会实例化

2.1 懒汉式——普通实现

#include 
using namespace std;

class CSingleton
{
private:
	static CSingleton* pInstance;

private:
	CSingleton():_num(0) {};
	~CSingleton() {};
	CSingleton(const CSingleton&) {};
	CSingleton& operator=(const CSingleton&) {};

public:
	void print() { std::cout << "_num:" << _num << std::endl; };

private:
	int _num;

public:
	// 定义公开方法提供一个全局访问点
	static CSingleton* getInstPtr()
	{
		if (!pInstance)
		{
			pInstance = new CSingleton();
		}
		return pInstance;
	}
	// 定义公开方法销毁实例对象
	static void DestroyInst()
	{
		if (pInstance)
		{
			delete pInstance;
		}
	}
	
	/*
	    这里我们采用静态方法来销毁实例对象, 同时我们也能采用嵌套类的方式来实现实例对象自动销毁
	    
	    我们将在下面做具体介绍, 有需要直接跳到  2.4 懒汉式——嵌套类实现
	*/
};
// 设置静态变量
CSingleton* CSingleton::pInstance = NULL;


int main(int argc, char *argv[])
{
	CSingleton* pInst = CSingleton::getInstPtr();
	pInst->print();
	CSingleton::DestroyInst();
	return 0;
}

分析:

  • 以上代码是比较典型的单例模式的实现方式, 但是并不是线程安全的, 在多线程的情况下,可能实例化多个对象
  • 所以我们需要对代码进行一些线程安全的处理

2.2 懒汉式——处理线程安全

关于线程同步机制,这里采用 c++11特性 (std::lock_guard等),这里不做具体介绍,可自行百度

#include 
#include 
using namespace std;

// 定义一个线程同步的全局标识
std::mutex g_lock;

class CSingleton
{
private:
	static CSingleton* pInstance;

private:
	CSingleton():_num(0) {};
	~CSingleton() {};
	CSingleton(const CSingleton&) {};
	CSingleton& operator=(const CSingleton&) {};

public:
	void print() { std::cout << "_num:" << _num << std::endl; };

private:
	int _num;

public:
	// 定义公开方法提供一个全局访问点
	static CSingleton* getInstPtr()
	{
		if (!pInstance)
		{
			lock_guard locker(g_lock);
			if (!pInstance)
			{
				pInstance = new CSingleton();
			}
		}
		return pInstance;
	}
	// 定义公开方法销毁实例对象
	static void DestroyInst()
	{
		if (pInstance)
		{
			delete pInstance;
		}
	}
};
// 设置静态变量
CSingleton* CSingleton::pInstance = NULL;


int main(int argc, char *argv[])
{
	CSingleton* pInst = CSingleton::getInstPtr();
	pInst->print();
	CSingleton::DestroyInst();
	return 0;
}

分析:

  • 在全局访问点中, 采用“双检锁”机制, 避免因为加锁释放锁带来的性能和资源消耗, 我们对静态实例指针对两次判空,只需要在第一次实例化实例化对象时加锁处理线程安全问题, 在后续的获取该实例时不需要再做无为的加锁工作

  • 这里已经可以满足一个类仅有一个实例对象的要求, 但是在对性能要求较高的情境下, 加锁的操作将会成为性能的瓶颈

ps: 作者在知乎看到有大神对这个线程原子性有不同的见解, 我也没有整明白, 暂时认定这样做没问题, 整清楚后再做分析

2.3 懒汉式——优化性能

#include 
using namespace std;

class CSingleton
{
private:
	CSingleton() :_num(0) {};
	~CSingleton() {};
	CSingleton(const CSingleton&) {};
	CSingleton& operator=(const CSingleton&) {};

public:
	void print() { std::cout << "_num:" << _num << std::endl; };

private:
	int _num;

public:
	// 定义公开方法提供一个全局访问点
	static CSingleton& getInstPtr()
	{
	    // 静态局部变量是线程安全的
		static CSingleton inst;
		return inst;
	}
};


int main(int argc, char *argv[])
{
	CSingleton &inst = CSingleton::getInstPtr();
	inst.print();
	return 0;
}

C++11规定 局部静态变量是线程安全的; 由于定义静态实例对象, 不需要再考虑内存释放问题

2.4 懒汉式——嵌套类实现

在上述代码中,我们都对内存释放问题做了处理,由于析构函数声明为private,不能显示调用 delete进行内存释放,所以我们引入静态函数 DestroyInst()

作者在《Effective C++》中了解到一种嵌套类写法,也能有效做到内存释放

#include 
using namespace std;

class CSingleton
{
private:
	static CSingleton* pInstance;

private:
	CSingleton() :_num(0) {};
	~CSingleton() {};
	CSingleton(const CSingleton&) {};
	CSingleton& operator=(const CSingleton&) {};

public:
	void print() { std::cout << "_num:" << _num << std::endl; };

private:
	int _num;

private:
    // 定义 Deletor静态私有类对象实例 
	class Deletor {
	public:
		~Deletor()
		{
			if (CSingleton::pInstance)
			{
				delete CSingleton::pInstance;
			}
		}
	};
	static Deletor _deletor;

public:
	// 定义公开方法提供一个全局访问点
	static CSingleton* getInstPtr()
	{
		if (!pInstance)
		{
			pInstance = new CSingleton();
		}
		return pInstance;
	}

};
// 设置静态变量
CSingleton* CSingleton::pInstance = NULL;


int main(int argc, char *argv[])
{
	CSingleton* pInst = CSingleton::getInstPtr();
	pInst->print();
	return 0;
}

分析:

  • 声明一个析构类,实例化私有静态对象,当程序结束时,自动调用静态对象的析构函数(~Deletor为 public),来释放单例对象

2. 饿汉式

饿汉式: 程序执行时立即初始化单例对象

2.1 饿汉式实现

#include 
using namespace std;

class CSingleton
{
private:
	static const CSingleton *pInstance;

private:
	CSingleton() {};
	~CSingleton() {};
	CSingleton(const CSingleton&) {};
	CSingleton& operator=(const CSingleton&) {};

public:
	// 定义公开方法提供一个全局访问点
	static CSingleton* getInstPtr()
	{
		return const_cast(pInstance);
	}

};
// 实例化静态单例对象
const CSingleton* CSingleton::pInstance = new CSingleton();

int main(int argc, char *argv[])
{
	CSingleton* pInst = CSingleton::getInstPtr();
	return 0;
}

分析:

  • 首先关于内存的释放不再重复, 前面代码已经做了相关处理, 需要的可自行查看添加
  • 在执行 main函数之前, 以单线程的方式完成对静态对象的实例化, 是线程安全的

ps: 还是在知乎上看到, 这里可能存在潜在问题, 待作者完全整明白再做刨析

3. 总结

  • 在上述的代码中, 我们也以较多的笔墨介绍在单例模式下如何释放内存的问题;其实作者认为在实际开发中并不会在乎这个实例销毁的问题,因为整个程序共享这一个单例对象,当软件结束时单例类对象的生命周期也就结束了,然后所占用的资源也会被释放,并不会存在所谓的内存泄露问题。
  • 当然为了代码的规范以及一些必须立即手动释放的资源,我们有必要做好内存的释放。
  • 这句话是最重要的,由于c++11的支持,作者极度推荐使用 2.3中讲到的局部静态变量的单例模式实现版本,舒适优雅。

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