【C++计划模式】第一篇:单例模式(Singleton)​

打印 上一主题 下一主题

主题 1014|帖子 1014|积分 3042

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
留意:复现代码时,确保 VS2022 使用 C++17/20 标准以支持现代特性。
确保全局唯一实例的线程安全实现


1. 模式定义与用途​

核心目标:保证一个类仅有一个实例,并提供全局访问点。
常见场景:


  • 日志体系(避免多个日志实例竞争文件资源)
  • 设置管理(统一读取和修改全局设置)
  • 硬件接口访问(如打印机装备控制)

2. 线程安全的现代 C++ 实现​

2.1 方法一:局部静态变量(Meyers’ Singleton,C++11起线程安全)

  1. #include <iostream>
  2. class Singleton {
  3. public:
  4.     // 删除拷贝构造函数和赋值操作符
  5.     Singleton(const Singleton&) = delete;
  6.     Singleton& operator=(const Singleton&) = delete;
  7.     // 获取唯一实例
  8.     static Singleton& getInstance() {
  9.         static Singleton instance; // 线程安全(C++11起)
  10.         return instance;
  11.     }
  12.     void logMessage(const std::string& message) {
  13.         std::cout << "Log: " << message << std::endl;
  14.     }
  15. private:
  16.     // 私有构造函数,禁止外部创建实例
  17.     Singleton() = default;
  18. };
  19. //测试代码
  20. int main() {
  21.     Singleton::getInstance().logMessage("System started.");
  22.     Singleton::getInstance().logMessage("User logged in.");
  23.     // 验证实例地址唯一
  24.     Singleton& s1 = Singleton::getInstance();
  25.     Singleton& s2 = Singleton::getInstance();
  26.     std::cout << "Addresses equal? " << (&s1 == &s2 ? "Yes" : "No") << std::endl; // 输出:Yes
  27.     return 0;
  28. }
复制代码
代码剖析:


  • 使用局部静态变量 static Singleton instance,C++11 标准保证其初始化线程安全。
  • 删除拷贝构造函数和赋值操纵符,防止意外复制实例。
  • 私有构造函数确保外部无法直接创建对象。

2.2 方法二:std::call_once(实用于需要动态初始化的场景)

  1. #include <iostream>
  2. #include <mutex>
  3. #include <memory>
  4. class Singleton {
  5. public:
  6.     Singleton(const Singleton&) = delete;
  7.     Singleton& operator=(const Singleton&) = delete;
  8.     static Singleton& getInstance() {
  9.         std::call_once(initFlag, []() {
  10.             instance = std::unique_ptr<Singleton>(new Singleton());
  11.         });
  12.         return *instance;
  13.     }
  14.     void logMessage(const std::string& message) {
  15.         std::cout << "Log: " << message << std::endl;
  16.     }
  17. private:
  18.     Singleton() = default;
  19.     static std::unique_ptr<Singleton> instance;
  20.     static std::once_flag initFlag;
  21. };
  22. // 静态成员初始化
  23. std::unique_ptr<Singleton> Singleton::instance = nullptr;
  24. std::once_flag Singleton::initFlag;
  25. // 测试代码(同上)
复制代码
代码剖析:


  • std::call_once 保证初始化代码仅实行一次,即使多线程情况下也安全。
  • 使用 std::unique_ptr 管理实例,避免内存泄漏。

3. 传统双检锁(Double-Checked Locking)的问题与改进​

旧版 C++(非线程安全)​:

  1. // 警告:C++11 前的实现可能存在线程安全问题!
  2. Singleton* Singleton::getInstance() {
  3.     if (instance == nullptr) {               // 第一次检查
  4.         std::lock_guard<std::mutex> lock(mutex);
  5.         if (instance == nullptr) {           // 第二次检查
  6.             instance = new Singleton();
  7.         }
  8.     }
  9.     return instance;
  10. }
复制代码
问题:


  • 内存读写顺序问题(指令重排可能导致未初始化完成的对象被访问)。
改进(C++11 起使用原子变量)​:
  1. #include <atomic>
  2. class Singleton {
  3.     static std::atomic<Singleton*> instance;
  4.     static std::mutex mutex;
  5. public:
  6.     static Singleton* getInstance() {
  7.         Singleton* tmp = instance.load(std::memory_order_acquire);
  8.         if (tmp == nullptr) {
  9.             std::lock_guard<std::mutex> lock(mutex);
  10.             tmp = instance.load(std::memory_order_relaxed);
  11.             if (tmp == nullptr) {
  12.                 tmp = new Singleton();
  13.                 instance.store(tmp, std::memory_order_release);
  14.             }
  15.         }
  16.         return tmp;
  17.     }
  18. };
复制代码

4. 应用场景示例:日志体系

  1. // 在 Singleton 类中添加日志文件操作
  2. #include <fstream>
  3. class Singleton {
  4.     // ...(同上)
  5. private:
  6.     std::ofstream logFile;
  7.     Singleton() {
  8.         logFile.open("app.log", std::ios::app);
  9.     }
  10. public:
  11.     void logMessage(const std::string& message) {
  12.         if (logFile.is_open()) {
  13.             logFile << message << std::endl;
  14.         }
  15.     }
  16.     ~Singleton() {
  17.         logFile.close();
  18.     }
  19. };
复制代码

5. 单例模式的优缺点

长处​​缺点全局唯一实例,避免资源冲突隐藏依靠关系,低沉代码可测试性耽误初始化(节省内存)长期持有资源可能影响程序退出举动
6. 总结与调试技巧



  • 验证线程安全:在 VS2022 中使用多线程调试,观察实例地址是否唯一。
  • 查察静态变量生命周期:通过断点检查局部静态变量的初始化机遇。
  • 禁用拷贝操纵:务必删除拷贝构造函数和赋值操纵符以防止意外复制

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

熊熊出没

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表