# C++设计模式:单例模式(Singleton Pattern) 你是否遇到过这样的需求: - • 📝 **日志系统**:整个应用程序只需要一个日志管理器,所有模块都向它写入日志 - • ⚙️ **配置管理**:程序启动时加载配置,全局共享同一份配置数据 - • 🗄️ **数据库连接**:需要一个全局的数据库连接管理器 - • 🎮 **游戏管理器**:游戏中的音效管理器、资源管理器等全局唯一对象 传统方法的问题: ``` // ❌ 问题1:全局变量,无法控制初始化顺序 Logger* g_logger = nullptr; // 可能在其他全局对象之前初始化失败 // ❌ 问题2:可以创建多个实例 Logger logger1; Logger logger2; // 两个日志对象,日志会混乱 // ❌ 问题3:线程不安全 if (!g_logger) { // 多线程环境下可能创建多个实例 g_logger = new Logger(); } ``` **单例模式**就是为了解决这些问题而生的!它确保一个类只有一个实例,并提供全局访问点。 ------ ## 🧠 单例模式核心概念 ### 什么是单例模式? **单例模式**是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来访问这个实例。 ### 单例模式的四个要素 1. 1. **私有构造函数**:防止外部直接创建实例 2. 2. **私有静态成员**:存储唯一实例 3. 3. **公有静态方法**:提供全局访问点 4. 4. **禁止拷贝和赋值**:确保只有一个实例 ### 单例模式的 UML 图 ``` ┌─────────────────────┐ │ Singleton │ ├─────────────────────┤ │ - instance: static │ ├─────────────────────┤ │ + getInstance() │ │ - Singleton() │ │ - Singleton(const&) │ │ - operator=(const&) │ └─────────────────────┘ ``` ------ ## 💻 单例模式的实现方式 ### 方式 1:懒汉式(Lazy Initialization)- 基础版本 **特点**:第一次使用时才创建实例 ``` #include #include class Logger { private: // 私有构造函数,防止外部创建 Logger() { std::cout << "Logger实例已创建\n"; } // 禁止拷贝构造和赋值 Logger(const Logger&) = delete; Logger& operator=(const Logger&) = delete; static Logger* instance; // 静态成员变量 public: // 全局访问点 static Logger* getInstance() { if (instance == nullptr) { instance = new Logger(); } return instance; } void log(const std::string& message) { std::cout << "[LOG] " << message << std::endl; } ~Logger() { std::cout << "Logger实例已销毁\n"; } }; // 静态成员变量初始化 Logger* Logger::instance = nullptr; // 使用示例 int main() { Logger* logger1 = Logger::getInstance(); Logger* logger2 = Logger::getInstance(); logger1->log("第一条日志"); logger2->log("第二条日志"); std::cout << "logger1地址: " << logger1 << std::endl; std::cout << "logger2地址: " << logger2 << std::endl; // 输出:两个地址相同,是同一个实例 return 0; } ``` **问题**: - • ❌ **线程不安全**:多线程环境下可能创建多个实例 - • ❌ **内存泄漏**:使用`new`分配,没有对应的`delete` ------ ### 方式 2:线程安全的懒汉式 - 双重检查锁定(Double-Checked Locking) **特点**:使用互斥锁保证线程安全 ``` #include #include class Logger { private: Logger() { std::cout << "Logger实例已创建\n"; } Logger(const Logger&) = delete; Logger& operator=(const Logger&) = delete; static Logger* instance; static std::mutex mtx; // 互斥锁 public: static Logger* getInstance() { // 第一次检查:避免每次都加锁 if (instance == nullptr) { std::lock_guard lock(mtx); // 加锁 // 第二次检查:确保只创建一个实例 if (instance == nullptr) { instance = new Logger(); } } return instance; } void log(const std::string& message) { std::cout << "[LOG] " << message << std::endl; } }; Logger* Logger::instance = nullptr; std::mutex Logger::mtx; // 使用示例 #include #include void threadFunction(int id) { Logger* logger = Logger::getInstance(); logger->log("线程 " + std::to_string(id) + " 的日志"); } int main() { std::vector threads; // 创建10个线程,每个都获取Logger实例 for (int i = 0; i < 10; ++i) { threads.emplace_back(threadFunction, i); } for (auto& t : threads) { t.join(); } return 0; } ``` **优点**: - • ✅ 线程安全 - • ✅ 性能优化(双重检查减少锁竞争) **缺点**: - • ⚠️ C++11 之前可能有内存可见性问题(需要`volatile`) - • ⚠️ 仍然存在内存管理问题 ------ ### 方式 3:使用 std::call_once(推荐) **特点**:C++11 标准库提供的线程安全初始化机制 ``` #include #include class Logger { private: Logger() { std::cout << "Logger实例已创建\n"; } Logger(const Logger&) = delete; Logger& operator=(const Logger&) = delete; static std::unique_ptr instance; static std::once_flag flag; // 保证只执行一次的标志 public: static Logger* getInstance() { // std::call_once确保只执行一次 std::call_once(flag, []() { instance = std::unique_ptr(new Logger()); }); return instance.get(); } void log(const std::string& message) { std::cout << "[LOG] " << message << std::endl; } }; std::unique_ptr Logger::instance = nullptr; std::once_flag Logger::flag; // 使用示例 int main() { Logger* logger1 = Logger::getInstance(); Logger* logger2 = Logger::getInstance(); logger1->log("测试日志"); // 两个指针指向同一个实例 std::cout << (logger1 == logger2 ? "是同一个实例" : "不是同一个实例") << std::endl; return 0; } ``` **优点**: - • ✅ 线程安全(标准库保证) - • ✅ 自动内存管理(使用`unique_ptr`) - • ✅ 代码简洁 ------ ### 方式 4:Meyers' Singleton(最推荐)⭐ **特点**:利用函数局部静态变量的特性,C++11 保证线程安全 ``` #include class Logger { private: Logger() { std::cout << "Logger实例已创建\n"; } Logger(const Logger&) = delete; Logger& operator=(const Logger&) = delete; public: // 返回引用而不是指针,更符合C++习惯 static Logger& getInstance() { // 函数局部静态变量,C++11保证线程安全初始化 static Logger instance; return instance; } void log(const std::string& message) { std::cout << "[LOG] " << message << std::endl; } ~Logger() { std::cout << "Logger实例已销毁\n"; } }; // 使用示例 int main() { Logger& logger1 = Logger::getInstance(); Logger& logger2 = Logger::getInstance(); logger1.log("第一条日志"); logger2.log("第二条日志"); // 两个引用指向同一个实例 std::cout << (&logger1 == &logger2 ? "是同一个实例" : "不是同一个实例") << std::endl; return 0; } ``` **优点**: - • ✅ **线程安全**:C++11 标准保证静态局部变量的初始化是线程安全的 - • ✅ **自动内存管理**:程序结束时自动销毁 - • ✅ **延迟初始化**:第一次调用时才创建 - • ✅ **代码最简洁**:无需手动管理内存和互斥锁 - • ✅ **异常安全**:如果构造失败,不会留下无效指针 **这是现代 C++中最推荐的单例实现方式!** ------ ## ⚠️ 单例模式的优缺点 ### ✅ 优点 1. 1. **全局唯一实例**:确保整个程序中只有一个实例 2. 2. **全局访问点**:提供统一的访问接口 3. 3. **延迟初始化**:第一次使用时才创建,节省资源 4. 4. **控制资源访问**:适合管理共享资源(数据库连接、文件句柄等) ### ❌ 缺点 1. 1. **违反单一职责原则**:类既要管理自己的业务逻辑,又要管理实例创建 2. 2. **难以测试**:全局状态使得单元测试困难 3. 3. **隐藏依赖关系**:全局访问使得依赖关系不明显 4. 4. **多线程复杂性**:需要仔细处理线程安全问题 5. 5. **全局状态**:可能导致代码耦合度高 ------ ## 🎯 何时使用单例模式 ### ✅ 适合使用的场景 1. 1. **日志系统**:整个应用只需要一个日志管理器 2. 2. **配置管理**:全局共享的配置数据 3. 3. **数据库连接池**:管理数据库连接资源 4. 4. **线程池**:管理线程资源 5. 5. **缓存系统**:全局共享的缓存 6. 6. **硬件资源**:打印机、显示器等硬件设备的访问 ### ❌ 不适合使用的场景 1. 1. **需要多个实例**:如果将来可能需要多个实例,不要使用单例 2. 2. **需要继承**:单例模式难以扩展和继承 3. 3. **需要多态**:单例模式限制了多态的使用 4. 4. **测试驱动开发**:全局状态使得测试困难 ------ ## 💡 最佳实践建议 ### 1. 优先使用 Meyers' Singleton ``` // ✅ 推荐:Meyers' Singleton class Singleton { public: static Singleton& getInstance() { static Singleton instance; return instance; } private: Singleton() = default; Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete; }; ``` ### 2. 返回引用而不是指针 ``` // ✅ 推荐:返回引用 static Singleton& getInstance(); // ❌ 不推荐:返回指针(需要检查nullptr) static Singleton* getInstance(); ``` ### 3. 明确禁止拷贝和赋值 ``` Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete; ``` ### 4. 考虑使用依赖注入替代 ``` // 更好的方式:依赖注入 class Service { public: Service(Logger& logger) : logger_(logger) {} private: Logger& logger_; }; ``` ------ ## 常见问题 Q:单例模式在多线程环境下安全吗? A:使用 Meyers' Singleton(函数局部静态变量)是线程安全的,C++11 标准保证静态局部变量的初始化是线程安全的。 Q:单例模式可以继承吗? A:可以,但需要小心设计。可以使用模板实现: ```cpp template class Singleton { protected: Singleton() = default; public: static T& getInstance(){ static T instance; return instance; } Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete; }; class MyClass : public Singleton { friend class Singleton; private: MyClass() = default; }; ``` ------ ## 📝 总结 单例模式是 C++中最常用的设计模式之一,它确保一个类只有一个实例,并提供全局访问点。 **核心要点**: 1. 1. **推荐实现**:使用 Meyers' Singleton(函数局部静态变量) 2. 2. **线程安全**:C++11 保证静态局部变量初始化的线程安全性 3. 3. **内存管理**:自动管理,无需手动释放 4. 4. **适用场景**:日志系统、配置管理、资源池等全局唯一对象 **现代 C++最佳实践**: ```cpp class Singleton { public: static Singleton& getInstance() { static Singleton instance; // C++11线程安全 return instance; } // 业务方法 void doSomething() {} private: Singleton() = default; Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete; }; ``` **记住**:单例模式是工具,不是目的。只有在真正需要全局唯一实例时才使用,避免过度设计!