单例模式是一种创建型设计模式,其核心是确保一个类在程序中只能存在唯一实例,并提供一个全局访问点。这种模式适用于需要集中管理资源(如日志、配置、连接池)的场景,避免资源冲突和重复创建的开销。
一、介绍
类型
单例模式的核心是限制实例化,但根据实例创建时机的不同,分为饿汉式 和懒汉式两种实现方式:
1. 饿汉式(Eager Initialization)
- 特点 :程序启动时(类加载阶段)就创建实例,无论是否使用。
- 优点:实现简单,天然线程安全(C++中全局变量初始化在主线程执行)。
- 缺点:如果实例占用资源大且始终未被使用,会造成资源浪费。
2. 懒汉式(Lazy Initialization)
- 特点 :第一次使用时才创建实例,延迟初始化。
- 优点:避免资源浪费,适合实例化成本高的场景。
- 缺点 :基础实现线程不安全,需要额外处理多线程同步问题。
选择饿汉式还是懒汉式,需根据实例化成本、使用频率和线程环境综合判断:资源占用小且必用→饿汉式;资源占用大或不一定使用→懒汉式。
单例模式的优点
- 唯一实例
- 确保类在整个程序中只有一个实例,避免资源冲突
- 全局访问
- 提供统一的访问点,方便在程序任何地方使用
- 资源控制
- 集中管理资源(如文件、网络连接),避免重复创建销毁
- 延迟初始化
- 只有在首次使用时才初始化,节省系统资源
- 线程安全(优化后)
- 现代实现可保证多线程环境下的安全性
单例模式的适用场景
- 全局资源管理器
- 日志管理器:确保所有日志写入同一文件/系统
- 配置管理器:全局共享一份配置数据
- 连接池:数据库/网络连接的统一管理
- 设备访问控制
- 打印机管理器:避免多个程序同时操作硬件
- 传感器接口:确保数据读取的一致性
- 全局状态存储
- 应用程序上下文:存储全局状态信息
- 缓存管理器:全局共享缓存数据
二、实现
以一个线程安全的日志管理器为例
cpp
#include <iostream>
#include <fstream>
#include <string>
#include <mutex>
#include <chrono>
#include <thread>
#include <sstream>
// 单例模式:日志管理器
class Logger {
private:
// 私有构造函数:防止外部实例化
Logger() {
logFile_.open("app.log", std::ios::app);
if (!logFile_.is_open()) {
throw std::runtime_error("无法打开日志文件");
}
log("Logger 初始化完成");
}
// 私有析构函数:防止外部销毁
~Logger() {
if (logFile_.is_open()) {
log("Logger 已关闭");
logFile_.close();
}
}
// 禁用拷贝构造和赋值运算符
Logger(const Logger&) = delete;
Logger& operator=(const Logger&) = delete;
// 禁用移动构造和移动赋值
Logger(Logger&&) = delete;
Logger& operator=(Logger&&) = delete;
// 日志文件流
std::ofstream logFile_;
// 线程安全互斥锁
mutable std::mutex mutex_;
// 获取当前时间字符串
std::string getCurrentTime() const {
auto now = std::chrono::system_clock::now();
std::time_t time = std::chrono::system_clock::to_time_t(now);
return std::ctime(&time);
}
public:
// 全局访问点:获取唯一实例
static Logger& getInstance() {
// 局部静态变量:C++11后保证线程安全初始化
static Logger instance;
return instance;
}
// 日志写入函数
void log(const std::string& message) const {
std::lock_guard<std::mutex> lock(mutex_); // 保证线程安全
if (logFile_.is_open()) {
logFile_ << "[" << getCurrentTime() << "] " << message << std::endl;
}
// 同时输出到控制台
std::cout << "[" << getCurrentTime() << "] " << message << std::endl;
}
};
// 测试多线程环境下单例的唯一性
void threadFunction(int threadId) {
std::stringstream ss;
ss << "线程 " << threadId << " 正在写入日志";
Logger::getInstance().log(ss.str());
// 模拟工作
std::this_thread::sleep_for(std::chrono::milliseconds(100));
ss.clear();
ss << "线程 " << threadId << " 完成工作";
Logger::getInstance().log(ss.str());
}
int main() {
try {
// 主线程日志
Logger::getInstance().log("程序启动");
// 创建多个线程测试
std::thread t1(threadFunction, 1);
std::thread t2(threadFunction, 2);
std::thread t3(threadFunction, 3);
// 等待线程完成
t1.join();
t2.join();
t3.join();
Logger::getInstance().log("程序退出");
} catch (const std::exception& e) {
std::cerr << "错误: " << e.what() << std::endl;
return 1;
}
return 0;
}
输出结果(示例)
[Thu Aug 22 10:00:00 2024
] Logger 初始化完成
[Thu Aug 22 10:00:00 2024
] 程序启动
[Thu Aug 22 10:00:00 2024
] 线程 1 正在写入日志
[Thu Aug 22 10:00:00 2024
] 线程 2 正在写入日志
[Thu Aug 22 10:00:00 2024
] 线程 3 正在写入日志
[Thu Aug 22 10:00:00 2024
] 线程 1 完成工作
[Thu Aug 22 10:00:00 2024
] 线程 2 完成工作
[Thu Aug 22 10:00:00 2024
] 线程 3 完成工作
[Thu Aug 22 10:00:00 2024
] 程序退出
[Thu Aug 22 10:00:00 2024
] Logger 已关闭
应用场景
- 日志系统
- 整个应用使用同一个日志实例,确保日志顺序和完整性
- 配置管理
- 读取配置文件后,全局共享配置信息,避免重复IO操作
- 数据库连接池
- 管理数据库连接的创建和复用,防止连接数爆炸
- GUI应用
- 主窗口实例:确保应用程序只有一个主窗口
- 对话框管理器:统一管理对话框的创建和销毁
- 硬件交互
- 如打印机、摄像头等设备的访问控制,避免冲突
三、优化
优化点
- 通用单例基类
- 使用CRTP(奇异递归模板模式)实现通用单例基类,避免重复代码
- 派生类只需继承
Singleton<Derived>
即可获得单例特性
- 增强的线程安全
- 采用双重检查锁定(DCLP)+ 原子变量,在保证线程安全的同时减少锁竞争
- C++11及以上标准确保局部静态变量初始化的线程安全性
- 资源管理优化
- 使用
std::unique_ptr
管理实例,确保自动释放资源,避免内存泄漏 - 提供
destroyInstance()
方法,支持在测试场景下手动销毁实例
- 使用
- 功能扩展
- 增加日志级别过滤,可动态设置日志输出粒度
- 支持带时间戳(精确到毫秒)和级别标记的日志格式
- 控制台输出支持彩色显示,区分不同日志级别
- 可测试性提升
- 允许手动销毁实例,支持单元测试中的状态重置
- 清晰的接口设计便于模拟(Mock)测试
- 现代C++特性
- 使用
std::atomic
确保指针操作的原子性 - 采用
constexpr
和类型安全的枚举类 - 使用
std::lock_guard
进行RAII风格的锁管理
- 使用
cpp
#include <iostream>
#include <fstream>
#include <string>
#include <mutex>
#include <chrono>
#include <thread>
#include <sstream>
#include <memory>
#include <atomic>
// 单例基类模板(CRTP模式:Curiously Recurring Template Pattern)
template <typename T>
class Singleton {
protected:
// 允许派生类构造
Singleton() = default;
// 禁止拷贝和移动
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
Singleton(Singleton&&) = delete;
Singleton& operator=(Singleton&&) = delete;
public:
// 全局访问点 - 线程安全的延迟初始化
static T& getInstance() {
// 双重检查锁定(DCLP)优化,减少锁竞争
if (!instance_.load(std::memory_order_acquire)) {
std::lock_guard<std::mutex> lock(mutex_);
if (!instance_.load(std::memory_order_relaxed)) {
static std::unique_ptr<T> instance(new T());
instance_.store(instance.get(), std::memory_order_release);
}
}
return *instance_.load(std::memory_order_acquire);
}
// 手动销毁实例(主要用于测试场景)
static void destroyInstance() {
std::lock_guard<std::mutex> lock(mutex_);
if (instance_.load(std::memory_order_relaxed)) {
instance_.store(nullptr, std::memory_order_release);
}
}
protected:
static std::atomic<T*> instance_; // 原子指针确保线程安全
static std::mutex mutex_; // 互斥锁
};
// 初始化静态成员
template <typename T>
std::atomic<T*> Singleton<T>::instance_(nullptr);
template <typename T>
std::mutex Singleton<T>::mutex_;
// 日志级别枚举
enum class LogLevel {
DEBUG,
INFO,
WARNING,
ERROR
};
// 具体单例类:日志管理器(继承自单例基类)
class Logger : public Singleton<Logger> {
// 允许基类访问私有构造函数
friend class Singleton<Logger>;
private:
std::ofstream logFile_;
mutable std::mutex writeMutex_; // 日志写入锁
LogLevel minLogLevel_; // 日志级别过滤
// 私有构造函数 - 初始化日志系统
Logger() : minLogLevel_(LogLevel::DEBUG) {
logFile_.open("app.log", std::ios::app);
if (!logFile_.is_open()) {
throw std::runtime_error("无法打开日志文件");
}
log(LogLevel::INFO, "Logger 初始化完成");
}
// 日志级别转字符串
std::string levelToString(LogLevel level) const {
switch (level) {
case LogLevel::DEBUG: return "DEBUG";
case LogLevel::INFO: return "INFO";
case LogLevel::WARNING: return "WARNING";
case LogLevel::ERROR: return "ERROR";
default: return "UNKNOWN";
}
}
// 获取当前时间字符串
std::string getCurrentTime() const {
auto now = std::chrono::system_clock::now();
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
now.time_since_epoch() % std::chrono::seconds(1)
);
std::time_t time = std::chrono::system_clock::to_time_t(now);
std::tm tm = *std::localtime(&time);
char buffer[32];
std::strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tm);
std::stringstream ss;
ss << buffer << "." << std::setw(3) << std::setfill('0') << ms.count();
return ss.str();
}
public:
// 设置日志级别过滤
void setLogLevel(LogLevel level) {
std::lock_guard<std::mutex> lock(writeMutex_);
minLogLevel_ = level;
}
// 日志写入函数(支持不同级别)
void log(LogLevel level, const std::string& message) const {
// 日志级别过滤
if (level < minLogLevel_) {
return;
}
std::lock_guard<std::mutex> lock(writeMutex_);
std::string timeStr = getCurrentTime();
std::string levelStr = levelToString(level);
std::string logMessage = "[" + timeStr + "] [" + levelStr + "] " + message;
// 写入文件
if (logFile_.is_open()) {
logFile_ << logMessage << std::endl;
}
// 控制台输出(不同级别不同颜色)
switch (level) {
case LogLevel::ERROR:
std::cerr << "\033[1;31m" << logMessage << "\033[0m" << std::endl;
break;
case LogLevel::WARNING:
std::cout << "\033[1;33m" << logMessage << "\033[0m" << std::endl;
break;
case LogLevel::INFO:
std::cout << "\033[1;32m" << logMessage << "\033[0m" << std::endl;
break;
default:
std::cout << logMessage << std::endl;
}
}
// 便捷日志函数
void debug(const std::string& message) const { log(LogLevel::DEBUG, message); }
void info(const std::string& message) const { log(LogLevel::INFO, message); }
void warning(const std::string& message) const { log(LogLevel::WARNING, message); }
void error(const std::string& message) const { log(LogLevel::ERROR, message); }
};
// 测试多线程环境
void threadTask(int threadId) {
Logger::getInstance().info("线程 " + std::to_string(threadId) + " 启动");
// 模拟工作
std::this_thread::sleep_for(std::chrono::milliseconds(100));
Logger::getInstance().debug("线程 " + std::to_string(threadId) + " 完成工作");
}
int main() {
try {
// 基本使用示例
Logger::getInstance().info("程序启动");
Logger::getInstance().setLogLevel(LogLevel::INFO); // 设置只显示INFO及以上级别
// 多线程测试
std::thread t1(threadTask, 1);
std::thread t2(threadTask, 2);
std::thread t3(threadTask, 3);
t1.join();
t2.join();
t3.join();
// 测试不同日志级别
Logger::getInstance().warning("这是一个警告");
Logger::getInstance().error("这是一个错误");
Logger::getInstance().debug("这个DEBUG日志不会显示(因为日志级别设置)");
Logger::getInstance().info("程序退出");
// 手动销毁(可选,主要用于测试)
Logger::destroyInstance();
} catch (const std::exception& e) {
std::cerr << "错误: " << e.what() << std::endl;
return 1;
}
return 0;
}
输出结果(示例)
[2024-08-22 15:30:00.123] [INFO] Logger 初始化完成
[2024-08-22 15:30:00.125] [INFO] 程序启动
[2024-08-22 15:30:00.126] [INFO] 线程 1 启动
[2024-08-22 15:30:00.127] [INFO] 线程 2 启动
[2024-08-22 15:30:00.128] [INFO] 线程 3 启动
[2024-08-22 15:30:00.230] [WARNING] 这是一个警告
[2024-08-22 15:30:00.231] [ERROR] 这是一个错误
[2024-08-22 15:30:00.232] [INFO] 程序退出
优化后的优势
- 更高的复用性
- 通用单例基类可被多个类复用,减少代码冗余
- 更好的性能
- 双重检查锁定减少了锁竞争,提高多线程环境下的性能
- 更灵活的功能
- 日志级别过滤等扩展功能使单例类更实用
- 更安全的资源管理
- 智能指针和RAII确保资源正确释放,避免内存泄漏
- 更好的可测试性
- 支持手动销毁实例,便于单元测试和状态重置