单例模式(Singleton Pattern)是设计模式中最简单但也是最常用的一种创建型模式,它确保一个类只有一个实例,并提供一个全局访问点。下面我将从多个维度全面解析C++中的单例模式实现。
核心要点:只初始化一次,需要有一个静态的getinstance接口 ,使用的时候不需要对象直接通过域名使用
一、基础实现
1. 经典实现(非线程安全)
class Singleton {
private:
static Singleton* instance; // 静态成员变量
Singleton() {} // 私有构造函数
~Singleton() {} // 私有析构函数
Singleton(const Singleton&) = delete; // 禁用拷贝构造
Singleton& operator=(const Singleton&) = delete; // 禁用赋值操作符
public:
static Singleton* getInstance() {
if (instance == nullptr) {
instance = new Singleton();
}
return instance;
}
};
// 静态成员初始化
Singleton* Singleton::instance = nullptr;
问题:非线程安全,多线程环境下可能创建多个实例
虽然静态函数还是同一个,但是多次创建会导致内存泄漏(前面创建的实例的静态函数的指针是空的),与及其他多个实例的非静态成员函数同时和一个静态函数交互会导致混乱。
二、线程安全实现
2.1 加锁实现(双重检查锁定)
#include <mutex>
class Singleton {
private:
static Singleton* instance;
static std::mutex mtx;
Singleton() {}
~Singleton() {}
public:
static Singleton* getInstance() {
if (instance == nullptr) { // 第一次检查
std::lock_guard<std::mutex> lock(mtx);
if (instance == nullptr) { // 第二次检查
instance = new Singleton();
}
}
return instance;
}
};
// 静态成员初始化
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mtx;
优点:线程安全且效率较高
注意:C++11前可能需要volatile
关键字防止指令重排
2.2 C++11局部静态变量实现(最推荐)
class Singleton {
private:
Singleton() {}
~Singleton() {}
public:
static Singleton& getInstance() {
static Singleton instance; // C++11保证静态局部变量初始化线程安全
return instance;
}
};
优点:
线程安全(C++11标准保证)
自动析构(程序结束时)
实现简单
延迟初始化(首次调用时构造)
三、高级主题
3.1 模板化单例
template<typename T>
class Singleton {
protected:
Singleton() = default;
~Singleton() = default;
public:
static T& getInstance() {
static T instance;
return instance;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
// 使用方式
class MyClass : public Singleton<MyClass> {
friend class Singleton<MyClass>;
private:
MyClass() {} // 构造函数必须私有
};
3.2 单例的销毁时机控制
class Singleton {
private:
Singleton() {}
~Singleton() {}
static Singleton& getRawInstance() {
static Singleton instance;
return instance;
}
public:
static Singleton& getInstance() {
return getRawInstance();
}
// 手动销毁接口(谨慎使用)
static void destroyInstance() {
// 实际无法真正销毁,但可以重置状态
}
};
3.3 多环境适配单例
#ifdef _WIN32
#include <windows.h>
#else
#include <pthread.h>
#endif
class CrossPlatformSingleton {
private:
static CrossPlatformSingleton* instance;
#ifdef _WIN32
static CRITICAL_SECTION cs;
#else
static pthread_mutex_t mutex;
#endif
CrossPlatformSingleton() {
#ifdef _WIN32
InitializeCriticalSection(&cs);
#else
pthread_mutex_init(&mutex, nullptr);
#endif
}
public:
static CrossPlatformSingleton* getInstance() {
if (instance == nullptr) {
#ifdef _WIN32
EnterCriticalSection(&cs);
#else
pthread_mutex_lock(&mutex);
#endif
if (instance == nullptr) {
instance = new CrossPlatformSingleton();
}
#ifdef _WIN32
LeaveCriticalSection(&cs);
#else
pthread_mutex_unlock(&mutex);
#endif
}
return instance;
}
};
四、单例模式的变体
4.1 多例模式(限制实例数量)
class Multiton {
private:
static const int MAX_INSTANCES = 3;
static std::array<Multiton*, MAX_INSTANCES> instances;
static std::mutex mtx;
static int nextIndex;
Multiton() {}
public:
static Multiton* getInstance() {
std::lock_guard<std::mutex> lock(mtx);
if (nextIndex < MAX_INSTANCES) {
instances[nextIndex] = new Multiton();
return instances[nextIndex++];
}
return instances[rand() % MAX_INSTANCES]; // 随机返回一个实例
}
};
4.2 线程局部单例(每个线程一个实例)
class ThreadLocalSingleton {
private:
ThreadLocalSingleton() {}
public:
static ThreadLocalSingleton& getInstance() {
thread_local ThreadLocalSingleton instance;
return instance;
}
};
五、单例模式的优缺点
优点:
严格控制实例数量
全局访问点方便管理
延迟初始化节省资源
避免频繁创建销毁对象
缺点:
违反单一职责原则(同时控制生命周期和业务逻辑)
难以扩展(需要修改代码而非配置)
隐藏类之间的依赖关系
对单元测试不友好(难以mock)
在多线程环境中需要特别注意线程安全
六、使用场景
配置管理:全局配置只需要一个实例
日志系统:整个应用共享一个日志管理器
设备驱动:如打印机假脱机系统
缓存系统:全局内存缓存
线程池:通常只需要一个线程池实例
七、现代C++最佳实践
优先使用局部静态变量实现(C++11及以上)
考虑使用std::call_once作为替代方案
class Singleton {
private:
static std::once_flag onceFlag;
static Singleton* instance;
Singleton() {}
public:
static Singleton& getInstance() {
std::call_once(onceFlag, []() {
instance = new Singleton();
});
return *instance;
}
};
3.对于需要参数的单例,可以使用以下模式:
class Config {
private:
std::string configPath;
Config(const std::string& path) : configPath(path) {}
public:
static Config& getInstance(const std::string& path = "") {
static Config instance(path.empty() ? "default.conf" : path);
return instance;
}
};
八、单例模式与依赖注入
在现代软件设计中,依赖注入(DI)框架常被用来替代单例模式,因为它能更好地解耦和测试:
// 使用依赖注入框架(如Google Fruit)
fruit::Injector<MyService> injector(getMyServiceComponent);
MyService* service = injector.get<MyService*>();
这种方式的优点是可以更容易地替换实现和进行单元测试。
总结
C++中的单例模式实现需要考虑线程安全、生命周期管理、可测试性等多方面因素。在C++11及以上版本中,推荐使用局部静态变量的实现方式,它简洁、安全且高效。对于更复杂的需求,可以考虑模板化单例或依赖注入等高级技术。