C++单例模式模板类

        在C++的代码中是要尽量避免使用全局变量的,全局变量可能在程序的任一地方被修改,提高代码的定位难度,还会导致代码耦合性变高,难以模块化测试。但有时候一个类对象想要全局使用,且全局只能初始化一次,这时就可以引入单例模式的思想。这里提到的只是单例模式应用的一个场景,实际上全局变量和单例没太大的相关性,单例和静态类的区别在这里不再赘述,网上可以查找到相关的对比,比如下面这个是C#上的说明:

单例模式和静态类 - kerwin cui - 博客园

一、单例模式

        在《Head First 设计模式》一书中,单例模式的定义是“确保一个类只有一个实例,并提供一个全局访问点”,类图如下:

C++单例模式模板类_第1张图片

        通过类图可以看到单例类有两个明显的特点:① 构造函数是私有的,这样在类外就不能随意进行实例化;② 有一个静态的函数,用来获取类内实例化的对象,类的静态函数可以在代码的任何地方进行调用。根据单例类的这两个特点,可以设计出两种经典的单例模式:懒汉式和饿汉式,还有基于懒汉式的线程安全的双检测锁模式,在下面这篇文章中有很详细的说明:

C++ 单例模式 - 知乎

二、双检测锁模式

        为了在多线程下使用单例模式,基于线程安全问题仅出现在第一次初始化(new)过程中,引入了双检锁模式。第一次检测不涉及到对象的创建,因此是不加锁的检测,不需要锁的时间消耗,只有检测到对象暂未创建的时候,才会进行对象的加锁操作,然后再进行数据的第二次检测,避免在加锁期间,对象已经创建成功,只有二次检测到对象尚未创建才会创建唯一的实例。

        为了在大规模的代码中,方便多个类都定义为单例类,且在写类的代码时不需要太大变动且模式统一,因此本文定义了一个模板类,通过可变模板参数和友元函数的概念,实现调用的统一,具体实现代码如下:

template 
class Singleton {
public:
	template
	static inline std::shared_ptr instance(Args&& ... args) {
		// 双检测锁模式
		if (!instance_.get()) {
			std::unique_lock lock(instanceMutex_);
			if (!instance_.get()) {
				instance_.reset(new T(std::forward(args)...));
			}
		}
		return instance_;
	}

private:
	Singleton() = default;
	virtual ~Singleton() = default;
	Singleton(const Singleton&) = default;
	Singleton& operator = (const Singleton&) = delete;

	// 实例
	static std::shared_ptr instance_;
	static std::mutex instanceMutex_;
};

template 
std::shared_ptr Singleton::instance_;

template 
std::mutex Singleton::instanceMutex_;

#define SINGLETON_DECL(type) \
    friend class std::shared_ptr< type >; \
    friend class Singleton< type >;

        双检锁模式可以在多线程下保证线程的安全,但并不是绝对的。某些内存模型、编译器的优化或者运行时优化等情况下,会先分配完内存再进行数据的构造,造成另一个线程如果调用getInstance()获取到一个不完全初始化的对象,从而出现崩溃的情况。这种情况一个是通过内存屏障(memory barrier)的方式解决,一个是在主函数或者是比较早期的时候就完成单例的创建,还有一个方式是atomic实现,可以参考第一节中《C++ 单例模式》链接里面讲解的很清晰,这里不再赘述,只提供一种单例模板类的实现思路。

三、单例模板类的调用

        将单例模板类定义在头文件Singleton.h中,其它头文件中想要定义为单例模式的类,只需要对其进行引用(#include Singleton.h)即可进行使用,假设我们想定义一个单例类A,则可以通过以下代码实现:

class A{
private:
    A() {}
	
public:
    ~A() {}
    
    int funA() {}
	
private:
    SINGLETON_DECL(A);
};


// 调用单例类A的公共成员函数
Singleton::instance()->funA();
// 在调用的时候会进行实例是否已创建的检查

        可以看到调用是比较简单且统一的,不需要针对每个类再单独写双检测锁的部分。单例模式的实现方式有很多,可以根据项目实际需求选择具体方式,不管是懒汉式、饿汉式还是优化实现,都可以通过可变参数模板进行全局的统一管理。

你可能感兴趣的:(c++,开发语言,设计模式,单例模式)