C++模板化单例类

1.为什么使用模板化单例类:


如果你需要为不同类型的对象实现单例,而没有使用模板化设计,那么你可能需要为每个类型都实现一个单例类,这会导致大量重复代码。模板化的单例类通过代码复用来减少这种冗余。将单例类设置为模板的主要目的是为了实现不同类型的单例,而无需为每个类型编写单独的单例类。模板提供了一种灵活且通用的方式来实现单例模式,使其能够适应不同类型的需求。在模板化单例中,每个类型会有自己的静态 _instance 成员变量,确保每个类型都有自己独立的单例实例,不会相互干扰。而且使用模板确保了在编译时就确定了单例的类型,不会出现类型不匹配的问题。这样可以保证类型安全,避免运行时类型错误。

2.模板化单例类代码实现:
 

template <typename T>
class Singleton
{
protected:
    Singleton()=default;  //希望基类继承的时候可以构造,所以设置为保护
    Singleton(const Singleton<T>&)=delete;
    Singleton& operator=(const Singleton<T>&)=delete;
    static std::shared_ptr<T> _instance;
public:
    static std::shared_ptr<T> GetInstance(){
         static std::once_flag s_flag;
         std::call_once(s_flag,[&](){
             _instance=std::shared_ptr<T>(new T);
         });
         return _instance;
    }
    void PrintAddress(){
        std::cout<<_instance.get()<<std::endl;
    }
    ~Singleton(){
        std::cout<<"this is Singleton destruct!"<<std::endl;
    }

};

template <typename T>
std::shared_ptr<T> Singleton<T>::_instance=nullptr;

3.三种单例类实现方式对比:

我们通过一个静态智能指针管理一个T类型的对象,可以用来实例出不同类型的单例,并且只有一个。同时,我们使用:

       

std::once_flag 是一个标志,用来确保 std::call_once 内的代码块只会被执行一次。

  • std::call_once 接受一个标志 s_flag 和一个要执行的代码块。在多线程环境中,std::call_once 确保只有一个线程会执行这个代码块,其他线程会阻塞,直到代码块执行完毕。
  • 线程安全std::call_once 能确保即使在多线程环境下,_instance 只会被初始化一次。
  • 高效性:一旦 std::call_once 完成初始化,之后的所有调用都会直接返回,不再涉及锁机制。

相比以前的空指针判断 + 双重检查锁定(懒汉模式):
 

if (_instance == nullptr) {
    std::lock_guard<std::mutex> lock(s_mutex);
    if (_instance == nullptr) {
        _instance = std::shared_ptr<T>(new T);
    }
}

这种方式易于理解,但是存在性能开销:即使 _instance 已经初始化完毕,每次检查时仍然需要锁定和解锁,这在高并发环境下可能带来性能开销。另外,由于new底层分配内存的操作(先分配内存,然后调用构造函数构造对象),在多线程环境下,可能会遇到这样的情况:线程A正在执行 new 操作,但在对象构造完成之前,线程B已经检测到 _instance 指针不为空。这会导致线程B访问未完全构造的对象,从而引发未定义行为。

还有个懒汉模式的变种,通过维护一个静态对象 ,并且在第一次使用时初始化:

    static Singleton& GetInstance() {
        static Singleton instance; // 第一次调用时创建实例
        return instance;
    }

这种方式也是既安全又节约资源的,但是静态局部变量的生命周期直到程序结束,因此可能导致实例的销毁时间较长,尽管如此,与双重检查锁定加 std::once_flag 相比,静态局部变量法没有额外的性能开销,它只在第一次调用时进行初始化,并且之后不会再进行额外的检查,是更推荐的选择。

最后,说说饿汉模式:

    static Singleton instance; // 在类加载时初始化
    static Singleton& getInstance() {
        return instance;
    }


Singleton Singleton::instance; // 类加载时创建实例

优点是简单,且线程安全。但缺点也很明显:类静态成员变量在程序启动时就会被初始化,即使你从未调用 getInstance() 方法,实例也已经存在。无论是否使用这个单例对象,它都会在程序启动时就创建,占用资源。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值