【设计模式】C++ 单例模式总结与最佳实践

1. 单例模式简介

单例模式(Singleton Pattern) 是软件开发中常见的设计模式之一,主要用于 确保某个类只有一个实例,并提供一个全局访问点。常见的使用场景包括:

  • 日志管理:全局唯一的日志记录器。
  • 数据库连接池:防止创建多个数据库连接,提高性能。
  • 资源管理器:如线程池、驱动管理器等。

2. 单例模式的实现方式

C++ 中实现单例模式的方式有多种,常见方式如下:

2.1 普通的单例模式(非线程安全)

特点

  • 使用静态成员变量存储唯一实例。
  • 懒加载(Lazy Initialization),即 首次访问时创建实例
  • 非线程安全,在多线程环境下可能创建多个实例。
cpp 复制代码
#include <iostream>

class Singleton {
private:
    static Singleton* instance;
    Singleton() { std::cout << "Singleton Instance Created\n"; }

public:
    static Singleton* getInstance() {
        if (instance == nullptr) {
            instance = new Singleton();
        }
        return instance;
    }
};

// 初始化静态成员变量
Singleton* Singleton::instance = nullptr;

int main() {
    Singleton* s1 = Singleton::getInstance();
    Singleton* s2 = Singleton::getInstance();
    std::cout << (s1 == s2) << "\n"; // 输出 1,表示同一个实例
}

缺点

  • 非线程安全 :多个线程可能同时调用 getInstance(),导致创建多个实例。
  • 内存泄漏:实例在程序结束时不会自动释放。

2.2 线程安全的单例模式

方法 1:使用互斥锁(Mutex)

特点

  • 通过 std::mutex 保护 getInstance(),确保多线程安全。
  • 缺点 :每次获取实例都要加锁,影响性能
cpp 复制代码
#include <iostream>
#include <mutex>

class Singleton {
private:
    static Singleton* instance;
    static std::mutex mutex;
    Singleton() { std::cout << "Singleton Instance Created\n"; }

public:
    static Singleton* getInstance() {
        std::lock_guard<std::mutex> lock(mutex);
        if (instance == nullptr) {
            instance = new Singleton();
        }
        return instance;
    }
};

// 初始化静态变量
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mutex;

方法 2:双重检查锁(DCLP)

特点

  • 提高性能 :只有在 instance == nullptr 时才会加锁。
  • C++11 及以后 推荐使用,早期 C++ 需要 volatile 避免指令重排。
cpp 复制代码
#include <iostream>
#include <mutex>

class Singleton {
private:
    static Singleton* instance;
    static std::mutex mutex;
    Singleton() {}

public:
    static Singleton* getInstance() {
        if (instance == nullptr) { // 第一次检查
            std::lock_guard<std::mutex> lock(mutex);
            if (instance == nullptr) { // 第二次检查
                instance = new Singleton();
            }
        }
        return instance;
    }
};

// 初始化静态变量
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mutex;

2.3 C++11 静态局部变量单例(推荐)

特点

  • C++11 保证静态局部变量初始化是线程安全的 ,不需要 mutex
  • 代码简洁,性能最佳,生命周期由编译器自动管理。
  • 唯一缺点:不能提前释放实例,程序结束时才释放。
cpp 复制代码
#include <iostream>

class Singleton {
private:
    Singleton() { std::cout << "Singleton Instance Created\n"; }

public:
    static Singleton& getInstance() {
        static Singleton instance; // C++11 线程安全
        return instance;
    }
};

int main() {
    Singleton& s1 = Singleton::getInstance();
    Singleton& s2 = Singleton::getInstance();
    std::cout << (&s1 == &s2) << "\n"; // 输出 1
}

3. 各种单例模式对比

实现方式 线程安全 性能 代码复杂度 生命周期管理
普通懒汉式 ❌ 否 ⭐⭐⭐ ⭐⭐⭐ 手动释放
加锁(mutex) ✅ 是 ⭐⭐ ⭐⭐⭐ 手动释放
双重检查锁(DCLP) ✅ 是 ⭐⭐⭐ ⭐⭐⭐⭐ 手动释放
静态局部变量(推荐) ✅ 是 ⭐⭐⭐⭐⭐ 自动释放

4. 结论:推荐使用 C++11 静态局部变量单例

如果你的 C++ 编译器支持 C++11 及以上推荐使用静态局部变量单例

  • 线程安全 ✅
  • 性能最佳 ✅
  • 代码简洁 ✅
  • 自动管理生命周期 ✅

适用场景

场景 适用单例方式
普通 C++ 项目 C++11 静态局部变量
多线程项目 C++11 静态局部变量 / DCLP
C++03 及以下 互斥锁(mutex)

如果需要手动管理对象生命周期

可以使用 std::unique_ptrstd::shared_ptr

cpp 复制代码
#include <memory>
class Singleton {
public:
    static std::shared_ptr<Singleton> getInstance() {
        static std::shared_ptr<Singleton> instance(new Singleton());
        return instance;
    }
};

5. 总结

C++ 单例模式有多种实现方式,C++11 之后,静态局部变量单例是最佳选择 ,它具备线程安全性、简洁性和性能优势。如果你在 旧版 C++ 或 MFC 中使用,则需要 手动加锁(mutex)双重检查锁(DCLP)

对于 MFC 开发,可以用 CCriticalSection 替代 std::mutex,确保兼容性。


你的选择?

你的项目用的是 C++11 以上吗?如果是,直接用 静态局部变量

如果你还在用 C++03,那就用 互斥锁(mutex)DCLP

相关推荐
码界奇点9 分钟前
Python Flask文件处理与异常处理实战指南
开发语言·python·自然语言处理·flask·python3.11
贩卖纯净水.18 分钟前
浏览器兼容-polyfill-本地服务-优化
开发语言·前端·javascript
weixin_4786897618 分钟前
C++ 对 C 的兼容性
java·c语言·c++
k要开心24 分钟前
C++概念以及基础框架语法
开发语言·c++
weixin_307779131 小时前
Linux下GCC和C++实现统计Clickhouse数据仓库指定表中各字段的空值、空字符串或零值比例
linux·运维·c++·数据仓库·clickhouse
开发者工具分享1 小时前
如何应对敏捷转型中的团队阻力
开发语言
gregmankiw1 小时前
C#调用Rust动态链接库DLL的案例
开发语言·rust·c#
roman_日积跬步-终至千里2 小时前
【Go语言基础【20】】Go的包与工程
开发语言·后端·golang
秦少游在淮海2 小时前
C++ - string 的使用 #auto #范围for #访问及遍历操作 #容量操作 #修改操作 #其他操作 #非成员函数
开发语言·c++·stl·string·范围for·auto·string 的使用
const5442 小时前
cpp自学 day2(—>运算符)
开发语言·c++