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