C++设计模式:单例模式(Singleton)(附日志记录器案例)
创作时间:
作者:
@小白创作中心
C++设计模式:单例模式(Singleton)(附日志记录器案例)
引用
CSDN
1.
https://m.blog.csdn.net/chenai886/article/details/143828193
什么是单例模式?
单例模式(Singleton)是一种创建型设计模式,旨在确保某个类在程序运行期间只有一个实例,并且提供一个全局访问点来使用该实例。这种模式在需要全局管理或共享资源的场景下非常有用。
单例模式的特点
- 唯一性:整个程序中,类的实例唯一存在。
- 全局访问点:可以通过一个静态方法访问该实例,便于全局调用。
- 延迟初始化:实例仅在第一次使用时创建,节省资源。
适用场景
单例模式适用于以下场景:
- 配置管理器:如读取和管理全局配置(数据库连接信息、文件路径等)。
- 日志记录器:用于记录日志,确保所有模块共享同一个日志实例。
- 线程池:全局统一管理线程的分配与回收。
- 硬件访问:如打印机驱动管理器,确保访问同一个硬件实例。
传统实现与问题
传统单例模式通常通过静态变量和手动加锁来实现。虽然可以保证实例唯一性,但这种方式在多线程环境下容易出现问题,需要额外的同步操作。
class Singleton {
private:
static Singleton* instance;
Singleton() {}
public:
static Singleton* getInstance() {
if (instance == nullptr) {
instance = new Singleton();
}
return instance;
}
};
Singleton* Singleton::instance = nullptr;
这种实现的主要问题是:
- 多线程不安全:多个线程可能同时创建实例。
- 手动管理复杂:需要额外的锁机制来保证线程安全。
改进的现代实现
自 C++11 起,语言提供了更便捷的工具来实现单例模式,推荐使用静态局部变量来创建线程安全的单例。
实际案例:日志记录器
以下是一个实际的日志记录器实现,通过单例模式确保全局唯一性,并实现简单的日志记录功能。
代码实现
#include <iostream>
#include <fstream>
#include <mutex>
#include <string>
// 日志记录器类:单例模式实现
class Logger {
public:
// 获取日志记录器的唯一实例
static Logger& getInstance() {
static Logger instance; // 静态局部变量,保证线程安全
return instance;
}
// 禁止拷贝构造和赋值操作
Logger(const Logger&) = delete;
Logger& operator=(const Logger&) = delete;
// 写日志方法
void log(const std::string& message) {
std::lock_guard<std::mutex> lock(logMutex); // 确保线程安全
logFile << message << std::endl;
std::cout << "日志已记录:" << message << std::endl; // 输出到控制台
}
private:
std::ofstream logFile; // 日志文件
std::mutex logMutex; // 用于多线程安全的互斥锁
// 私有构造函数
Logger() {
logFile.open("application.log", std::ios::app); // 追加模式打开日志文件
if (!logFile.is_open()) {
std::cerr << "无法打开日志文件!" << std::endl;
} else {
std::cout << "日志记录器初始化成功。" << std::endl;
}
}
// 私有析构函数
~Logger() {
if (logFile.is_open()) {
logFile.close();
}
}
};
// 模拟多线程写日志
#include <thread>
void simulateLogging(const std::string& threadName) {
Logger& logger = Logger::getInstance();
for (int i = 1; i <= 5; ++i) {
logger.log(threadName + " - 日志消息 " + std::to_string(i));
}
}
int main() {
// 主线程写日志
Logger& logger = Logger::getInstance();
logger.log("主线程开始运行...");
// 创建多个线程模拟并发写日志
std::thread thread1(simulateLogging, "线程1");
std::thread thread2(simulateLogging, "线程2");
// 等待线程完成
thread1.join();
thread2.join();
// 主线程结束
logger.log("主线程运行结束。");
return 0;
}
代码解析
- 单例模式的实现
- 通过静态局部变量
static Logger instance,确保全局只有一个日志记录器实例。 - 禁止拷贝构造和赋值操作,防止生成额外实例。
- 通过静态局部变量
- 线程安全性
- 使用
std::mutex和std::lock_guard,保证多线程同时写日志时不会出现竞争条件。
- 使用
- 日志输出
- 日志写入文件
application.log,并实时输出到控制台,方便调试。
- 日志写入文件
运行结果
控制台输出
日志记录器初始化成功。
日志已记录:主线程开始运行...
日志已记录:线程1 - 日志消息 1
日志已记录:线程2 - 日志消息 1
日志已记录:线程1 - 日志消息 2
日志已记录:线程2 - 日志消息 2
日志已记录:线程1 - 日志消息 3
日志已记录:线程2 - 日志消息 3
日志已记录:线程1 - 日志消息 4
日志已记录:线程2 - 日志消息 4
日志已记录:线程1 - 日志消息 5
日志已记录:线程2 - 日志消息 5
日志已记录:主线程运行结束。
日志文件内容(application.log)
主线程开始运行...
线程1 - 日志消息 1
线程2 - 日志消息 1
线程1 - 日志消息 2
线程2 - 日志消息 2
线程1 - 日志消息 3
线程2 - 日志消息 3
线程1 - 日志消息 4
线程2 - 日志消息 4
线程1 - 日志消息 5
线程2 - 日志消息 5
主线程运行结束。
优缺点分析
优点
- 全局唯一性:整个程序中,日志记录器通过
Logger::getInstance()方法访问,确保只有一个实例。 - 线程安全:通过
std::mutex确保日志操作不会因多线程引发数据竞争。 - 模块化与扩展性:可以轻松扩展日志记录器功能,如添加日志级别(INFO、DEBUG、ERROR)、日志格式化等。
缺点
- 隐藏依赖性:单例通过全局访问点可能增加模块之间的耦合性。
- 不易测试:单例模式的全局状态共享可能影响单元测试的独立性。
总结
日志记录器是单例模式的经典应用场景。通过单例模式,可以轻松实现全局统一管理、线程安全和资源共享。在多线程程序中,使用 C++11 的静态局部变量和互斥锁,可以确保日志记录器的安全性和稳定性。此代码是一个实际的应用案例,具有很高的实用性,可直接应用于实际开发中,同时也为单例模式的深入理解提供了很好的实践支持。
热门推荐
加班费在劳动合同中的约定与履行
在设计项目中,如何与客户进行有效的沟通和需求理解
黑海东头望大秦:亚欧佛教、宗教交流与合作
解锁心理学硕士的力量,懂人性才能赢未来!
PNAS: 持续而稳定的湿润气候促进秦-西汉王朝社会经济繁荣
美国胡佛大坝争议:生态保护与人类发展的平衡术
十大好吃的简易营养便当 10种最受欢迎盒饭做法
揭秘金融市场的神秘龟甲,海龟交易法则深度解析
叶酸与 5-甲基四氢叶酸的生物学特征与应用,活性叶酸选择指南
酸模的功效与种植方法:一种兼具观赏、食用和药用价值的植物
如何将视频从PC传输到iPhone:iTunes及其他多种方法详解
iPhone和电脑之间互传文件的三种方法
酒后晨起口臭 背后的“神秘力量”
刮痧的正确使用方法
刮痧注意事项:这些关键点你必须知道
如何准确计算股息股利收益?这些收益计算方法有哪些应用场景?
揭开能量秘密:营养成分表中的能量如何计算?
如何通过股票的技术指标筛选优质股
哪吒当红!数字文创产业如何撬动商业地产新增长?
“汇资源+建平台+优服务” 成都高新区打造国内领先数字文创产业高地
陈皮怎么吃
开心消消乐全关卡攻略:从入门到精通的完整指南
军工箱:坚不可摧的装备守护者
微软与OpenAI合作关系演变的启示
骆宾王七岁写出成名作,长大后的他怎么样了?
天玑9300和骁龙8gen3哪个好?巅峰对决!一文告诉你谁是性能之王
茯砖茶的正确冲泡方法,这几点很重要!
肺结核复发几率大吗?三个关键因素及预防措施
Ubuntu的源管理详解
自己做好吃的鸡爪吧