单例模板使用

C++实现单例模板类

单例模板类是一个高度通用的工具类,配合友元类访问、shared_ptr 和 std::call_once,可以高效且安全地为任意类提供延迟创建的唯一实例,适合配置管理器、资源加载器等场景。

代码思路

我们希望有一个类模板,能够让一个类型T对应唯一一个实例,而且要是线程安全的。

// 
#include <memory>
#include <mutex>
template <typename T>
class Singleton {
public:
    static std::shared_ptr<T> getInstance();
};

大概是这样的一个类,但是既然它是一个单例类的模板,也就是不允许外部去使用它的构造函数,但是又希望它的子类能够使用构造函数。所以将构造函数和析构函数声明为protected,这样就彻底禁止了外部手动创建和销毁实例,从而保证了单例对象的生命周期由系统或容器(如 shared_ptr)管理。

// 
#include <memory>
#include <mutex>
template <typename T>
class Singleton {
public:
    static std::shared_ptr<T> getInstance();
protected:
	Singleton() = default;
	~Singleton() = default;
};

此外,为了保证单例身份,避免复制多个实例,还需要禁用模板类的拷贝构造函数和赋值构造函数。

#include <memory>
#include <mutex>
// 禁用拷贝构造和赋值构造
template <typename T>
class Singleton {
public:
    static std::shared_ptr<T> getInstance();
protected:
	Singleton() = default;
	~Singleton() = default;
	Singleton(const Singleton<T> &) = delete;
	Singleton & operator = (const Singleton<T> & st) = delete;
};

除此以外,我们还需要保证我们所实现的单例类是线程安全的,也就是希望在多线程环境下,我们依然只创建一个实例,这个实例应该一般都是用于做管理类,所以只需要一个,因此在这里我们使用once_flag和call _once关键字来保证在多线程环境下保证某段代码只执行一次,比使用锁的方式要更加方便。

#include <memory>
#include <mutex>
// 保证线程安全
template <typename T>
class Singleton {
public:
    static std::shared_ptr<T> getInstance(){
    	static std::once_flag Flag;//需要声明为静态变量
    	std::call_once(Flag,[](){
    		_instance = std::shared_ptr<T>(new T());
    	});
    	return _instance;
    };
protected:
	Singleton() = default;
	~Singleton() = default;
	Singleton(const Singleton<T> &) = delete;
	Singleton & operator = (const Singleton<T> & st) = delete;
	static std::shared_ptr <T> _instance;
};
//这里由于我们声明了静态变量,所以我们需要在类外进行初始化,因为静态变量的初始化是在编译准备期间的
template <typename T>
std::shared_ptr<T> Singleton<T>:: _instance = nullptr;

这里可以思考,为什么call_once中执行的代码段_instance = std::shared_ptr<T>(new T());为什么要使用 shared_ptr,而不是使用make_shared

为什么使用shared_ptr,而不是使用make_shared?

因为Singleton的构造函数一般是protected或者private的,而make_shared<T>是一个模板类函数,它是在std命名空间里定义的,并不是Singleton的友元或成员,因此无法访问私有的构造函数,但是new T()可以在Singleton<T>的成员函数中调用。如果使用make_shared<T>()就会造成构造函数无法访问的错误。

CRTP奇异递归模板使用单例

CRTP 是一种使用模板实现 静态多态 的技术,让你在编译期就决定函数调用、减少运行时开销,同时还能增强代码复用能力,简单来说,就是一个类继承了一个以自己为模板参数的基类。

#include "Singleton.h"
Class Httpmgr : public Singleton<Httpmgr>{
public:
	~Httpmgr();
private:
	Httpmgr();
}

构造函数因为是一个单例模式,所以必须声明为私有函数,防止外部去进行构造,这里也解释了为什么我们要在模板类中,将模板类的构造函数声明为protected,就是为了让子类可以去调用基类的构造函数。

为什么这里析构函数要声明为public

当执行Singleton的析构时,肯定会回收它的成员变量,由于static std::shared_ptr <T> _instance是Singleton的静态成员变量,此时回收就会调用共享指针的析构,其内部又会调用T的析构,这个T指的就是Httpmgr这个类,Httpmgr是子类,所以需要将析构函数设置为public。
此外,由于我们在Singleton中的获取单例的getInstance()函数中使用了_instance = std::shared_ptr<T>(new T());这里的T是Httpmgr,也就是说这里我们需要执行Httpmgr的构造函数,但是这里Httpmgr的构造函数已经被我们声明为private来保证其单例性了,所以这个问题就是基类想要调用子类的私有构造函数。可以采用:

#include "Singleton.h"
Class Httpmgr : public Singleton<Httpmgr>{
public:
	~Httpmgr();
private:
	friend class Singleton<Httpmgr>;//声明为友元
	Httpmgr();
}

将基类声明为友元的方法,让基类能够调用子类的私有构造函数。

CRTP这种奇异递归模板继承的是自己的单例模板类,但是自身都没有进行构造,没有进行初始化,怎么就能继承自己?

CRTP 中看似“类继承了自己”,其实并没有构造递归对象,而只是将派生类的类型名作为模板参数传入父类,等到编译完成后再整体展开 —— 是一种类型级的递归声明,不是运行时递归行为。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值