最近在项目上用到了单例,突发奇想想写一个模板单例基类用于注册需要使用的单例类。就来复习一下什么是单例模式吧。

什么是单例模式

定义单例模式的核心概念:确保一个类只有一个实例,并提供全局访问点。
适用场景:需要全局唯一对象的场景,如配置管理、线程池、日志系统等。

单例模式的适用场景

  • 资源共享:如数据库连接池、线程池。
  • 配置管理:全局配置对象避免重复加载。
  • 控制实例数量:避免资源冲突或性能开销。

传统实现方式

饿汉模式

特点:类加载时即初始化实例,线程安全但可能浪费资源。

注意事项:在C++11之前,这种经典的饿汉模式不是线程安全的,且析构顺序不确定。
代码示例:

class Singleton {
private:
    static Singleton instance; // 静态成员变量
    Singleton() {} // 私有构造函数
public:
    static Singleton& getInstance() {
        return instance;
    }
};
// 静态成员初始化
Singleton Singleton::instance;  //传统static变量必须类内声明,类外初始化


//也可以写成,区别不大
class Singleton {
private:
    Singleton() {} // 私有构造函数
public:
    static Singleton& getInstance() {
        static Singleton instance; // 静态成员变量
        return instance;
    }
};

懒汉模式(非线程安全)

特点:延迟初始化,在使用到的时候才去初始化。但多线程下可能重复创建实例。需要手动释放资源。但这也保证了析构顺序和释放时机是我们完全可控的。
代码示例:

class Singleton {
private:
    static Singleton* instance;
    Singleton() {}
public:
    static Singleton* getInstance() {
        if (!instance) {
            instance = new Singleton();
        }
        return instance;
    }
};
// 静态成员初始化
Singleton* Singleton::instance = nullptr;

线程安全的懒汉模式(加锁)

通过互斥锁保证线程安全,但锁开销可能影响性能。
代码示例:

#include <mutex>
class Singleton {
private:
    static Singleton* instance;
    static std::mutex mtx;
    Singleton() {}
public:
    static Singleton* getInstance() {
        std::lock_guard<std::mutex> lock(mtx);
        if (!instance) {
            instance = new Singleton();
        }
        return instance;
    }
};

后面为了避免单一互斥锁在多线程环境下带来的性能损耗,引入了双重检测,在保证线程安全的同时又减少了锁的竞争

class Singleton {
private:
    static std::atomic<Singleton*> instance;
    static std::mutex mtx;

    Singleton() {}
public:
    static Singleton* getInstance() {
        Singleton* tmp = instance.load(std::memory_order_acquire);
        if (!tmp) { // 第一次检测
            std::lock_guard<std::mutex> lock(mtx);
            tmp = instance.load(std::memory_order_relaxed);
            if (!tmp) { // 第二次检测
                tmp = new Singleton();
                instance.store(tmp, std::memory_order_release);
            }
        }
        return tmp;
    }
};

现代C++实现优化

静态局部变量(C++11线程安全)--最推荐

利用C++11的静态局部变量特性,实现简洁且线程安全的懒汉模式。
代码示例:

class Singleton {
private:
    Singleton() {}
public:
    static Singleton& getInstance() {
        static Singleton instance; // 线程安全初始化
        return instance;
    }
};

智能指针管理单例

使用std::shared_ptrstd::unique_ptr管理动态分配的单例,避免内存泄漏。
代码示例:

#include <memory>
#include <mutex>

class Singleton {
private:
    static std::shared_ptr<Singleton> instance;
    static std::mutex mtx;
    Singleton() {}

public:
    static std::shared_ptr<Singleton> getInstance() {
        if (!instance) {
            std::lock_guard<std::mutex> lock(mtx);
            if (!instance) {
                instance.reset(new Singleton());   //注意这里只能用new去初始化,而不能用make_shared
            }
        }
        return instance;
    }
};

// 初始化静态成员
std::shared_ptr<Singleton> Singleton::instance = nullptr;
std::mutex Singleton::mtx;

这里做个了解即可,因为C++11后,局部静态变量方式的单例就已经很安全,并且能应对大部分使用场景了。

单例模板

#include <iostream>
#include <memory>

template <typename T>

class Singleton{

 //让子类可构造父类,设为保护
protected:

      Singleton() = default;

      Singleton(const Singleton<T>& ) = delete;   //禁止拷贝构造
      Singleton& operator = (const Singleton<T>& st) = delete;    //禁止拷贝赋值运算符
public:
 
      static T& GetInstance(){

          static T _Instance;
          return _Instance;
      }

       //正常单例模式构造和析构都要私有,但模板里析构必须公有!
       ~Singleton()  //防止外部释放
      {
          std::cout<<"this Singleton is delete"<<std::endl;
      }
};

这里要注意模板单例类的析构函数必须公有!,这是因为C++ 中派生类对象的析构会依次调用派生类和基类的析构函数。若基类析构函数不可访问(如私有),编译器无法生成派生类析构函数的隐式调用代码。也可以设置为保护成员,需要在子类里将基类设置为友元。

模板使用方式

class Test_SubSingleton: public QObject,public Singleton<Test_SubSingleton> ,public std::enable_shared_from_this<Test_SubSingleton>
{
    Q_OBJECT
public:
    ~Test_SubSingleton(); 

private:
    friend Singleton<HttpMgr>; //将基类声明为子类的友元,这样基类就能访问子类的私有和保护函数
    Test_SubSingleton();

};

这里继承时使用到了CRTP(奇异递归模板),允许我们继承自一个自身类型为模板参数的类。感兴趣的可自行了解,这里不过多展开了。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐