C++设计模式之单例模式
·
最近在项目上用到了单例,突发奇想想写一个模板单例基类用于注册需要使用的单例类。就来复习一下什么是单例模式吧。
什么是单例模式
定义单例模式的核心概念:确保一个类只有一个实例,并提供全局访问点。
适用场景:需要全局唯一对象的场景,如配置管理、线程池、日志系统等。
单例模式的适用场景
- 资源共享:如数据库连接池、线程池。
- 配置管理:全局配置对象避免重复加载。
- 控制实例数量:避免资源冲突或性能开销。
传统实现方式
饿汉模式
特点:类加载时即初始化实例,线程安全但可能浪费资源。
注意事项:在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_ptr或std::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(奇异递归模板),允许我们继承自一个自身类型为模板参数的类。感兴趣的可自行了解,这里不过多展开了。
更多推荐


所有评论(0)