文章目录
-
- 前言:为什么你需要学习单例模式?
- 第一章:单例模式是什么?用生活化例子理解
-
- [1.1 核心概念:一个类只有一个实例](#1.1 核心概念:一个类只有一个实例)
- [1.2 现实生活中的单例模式](#1.2 现实生活中的单例模式)
- 第二章:单例模式详解
-
- [2.1 基本实现思路](#2.1 基本实现思路)
- [2.2 饿汉式单例(Eager Initialization)](#2.2 饿汉式单例(Eager Initialization))
- [2.3 懒汉式单例(Lazy Initialization)](#2.3 懒汉式单例(Lazy Initialization))
- [2.4 线程安全的懒汉式单例(C++11之后)](#2.4 线程安全的懒汉式单例(C++11之后))
- [2.5 双重检查锁单例(Double-Checked Locking)](#2.5 双重检查锁单例(Double-Checked Locking))
- [2.6 Meyers' Singleton(推荐使用)](#2.6 Meyers' Singleton(推荐使用))
- [2.7 返回指针的Meyers' Singleton变体](#2.7 返回指针的Meyers' Singleton变体)
- 第三章:C++单例模式的实现方式对比
-
- [3.1 各种实现方式比较](#3.1 各种实现方式比较)
- [3.2 如何选择实现方式?](#3.2 如何选择实现方式?)
- 第四章:实际项目中的应用场景
-
- [4.1 配置文件管理器](#4.1 配置文件管理器)
- [4.2 日志记录器](#4.2 日志记录器)
- [4.3 缓存管理器](#4.3 缓存管理器)
- 第五章:C++单例模式的特有问题和解决方案
-
- [5.1 静态初始化顺序问题](#5.1 静态初始化顺序问题)
- [5.2 单例的依赖关系管理](#5.2 单例的依赖关系管理)
- 第六章:单例模式的优缺点
-
- [6.1 优点 ✅](#6.1 优点 ✅)
- [6.2 缺点 ❌](#6.2 缺点 ❌)
- 第七章:最佳实践和常见陷阱
-
- [7.1 最佳实践 ✅](#7.1 最佳实践 ✅)
- [7.2 常见陷阱 ❌](#7.2 常见陷阱 ❌)
- 第八章:现代C++中的单例模式替代方案
-
- [8.1 使用依赖注入框架](#8.1 使用依赖注入框架)
- [8.2 使用命名空间和静态函数](#8.2 使用命名空间和静态函数)
- 第九章:实战练习
-
- 练习1:实现一个线程安全的配置管理器
- 练习2:实现一个数据库连接池单例
- 练习3:实现一个支持模板的单例基类
- [练习4:使用Meyers' Singleton实现一个日志记录器](#练习4:使用Meyers' Singleton实现一个日志记录器)
- 总结
前言:为什么你需要学习单例模式?
想象一下,如果一个公司有多个CEO同时发号施令,或者一个系统中有多个配置管理器各自为政...这肯定会造成混乱!在C++编程中,单例模式就是确保"唯一性"的智慧。
学完本教程,你将:
- 真正理解单例模式的核心思想和应用场景
- 掌握C++中多种单例模式的实现方式及其优缺点
- 能在实际项目中正确使用单例模式
- 避免单例模式的常见陷阱和误用
第一章:单例模式是什么?用生活化例子理解
1.1 核心概念:一个类只有一个实例
单例模式的本质: 保证一个类只有一个实例,并提供一个全局访问点。
1.2 现实生活中的单例模式
场景1:公司CEO 🏢
- 一个公司只能有一个CEO
- 所有决策都要通过CEO
- 确保公司战略的一致性
场景2:操作系统任务管理器 💻
- 整个系统只需要一个任务管理器实例
- 统一管理所有进程和资源
- 避免资源管理的冲突
场景3:数据库连接池 🗄️
- 整个应用共享一个连接池实例
- 统一管理数据库连接资源
- 提高资源利用率
场景4:系统配置管理器 ⚙️
- 应用运行期间配置信息只需加载一次
- 所有模块共享同一份配置
- 保证配置的一致性
第二章:单例模式详解
2.1 基本实现思路
单例模式的核心要点:
- 私有化构造函数 - 防止外部直接创建实例
- 静态私有实例变量 - 保存唯一的实例
- 静态公有访问方法 - 提供全局访问点
2.2 饿汉式单例(Eager Initialization)
cpp
/**
* 饿汉式单例 - 程序启动时就创建实例
* 优点:简单、线程安全
* 缺点:如果不用会浪费内存
*/
class EagerSingleton {
private:
// 1. 私有静态实例,程序启动时立即创建
static EagerSingleton instance;
// 2. 私有构造函数,防止外部实例化
EagerSingleton() {
std::cout << "饿汉式单例实例被创建" << std::endl;
}
// 3. 防止拷贝和赋值
EagerSingleton(const EagerSingleton&) = delete;
EagerSingleton& operator=(const EagerSingleton&) = delete;
public:
// 4. 公有静态方法,提供全局访问点
static EagerSingleton& getInstance() {
return instance;
}
// 业务方法
void showMessage() {
std::cout << "Hello from Eager Singleton!" << std::endl;
}
};
// 在类外定义静态成员变量
EagerSingleton EagerSingleton::instance;
// 使用示例
int main() {
// 多次获取实例,实际上都是同一个对象
EagerSingleton& singleton1 = EagerSingleton::getInstance();
EagerSingleton& singleton2 = EagerSingleton::getInstance();
singleton1.showMessage();
singleton2.showMessage();
// 验证是否是同一个实例
std::cout << "是否是同一个实例: " << (&singleton1 == &singleton2) << std::endl;
// 输出:1 (true)
return 0;
}
2.3 懒汉式单例(Lazy Initialization)
cpp
/**
* 懒汉式单例 - 第一次使用时才创建实例
* 优点:内存使用更高效
* 缺点:需要处理线程安全问题
*/
class LazySingleton {
private:
static LazySingleton* instance;
LazySingleton() {
std::cout << "懒汉式单例实例被创建" << std::endl;
}
// 防止拷贝和赋值
LazySingleton(const LazySingleton&) = delete;
LazySingleton& operator=(const LazySingleton&) = delete;
public:
// 非线程安全版本
static LazySingleton* getInstance() {
if (instance == nullptr) {
instance = new LazySingleton();
}
return instance;
}
void showMessage() {
std::cout << "Hello from Lazy Singleton!" << std::endl;
}
// 记得释放内存
static void destroy() {
if (instance != nullptr) {
delete instance;
instance = nullptr;
}
}
};
// 静态成员初始化
LazySingleton* LazySingleton::instance = nullptr;
2.4 线程安全的懒汉式单例(C++11之后)
cpp
#include <mutex>
/**
* 线程安全的懒汉式单例 - 使用C++11的mutex
*/
class ThreadSafeLazySingleton {
private:
static ThreadSafeLazySingleton* instance;
static std::mutex mtx;
ThreadSafeLazySingleton() {
std::cout << "线程安全懒汉式单例实例被创建" << std::endl;
}
ThreadSafeLazySingleton(const ThreadSafeLazySingleton&) = delete;
ThreadSafeLazySingleton& operator=(const ThreadSafeLazySingleton&) = delete;
public:
// 方法级别同步
static ThreadSafeLazySingleton* getInstance() {
std::lock_guard<std::mutex> lock(mtx);
if (instance == nullptr) {
instance = new ThreadSafeLazySingleton();
}
return instance;
}
void showMessage() {
std::cout << "Hello from Thread Safe Lazy Singleton!" << std::endl;
}
static void destroy() {
std::lock_guard<std::mutex> lock(mtx);
if (instance != nullptr) {
delete instance;
instance = nullptr;
}
}
};
// 静态成员初始化
ThreadSafeLazySingleton* ThreadSafeLazySingleton::instance = nullptr;
std::mutex ThreadSafeLazySingleton::mtx;
2.5 双重检查锁单例(Double-Checked Locking)
cpp
/**
* 双重检查锁单例 - 更好的性能
*/
class DoubleCheckedSingleton {
private:
static std::atomic<DoubleCheckedSingleton*> instance;
static std::mutex mtx;
DoubleCheckedSingleton() {
std::cout << "双重检查锁单例实例被创建" << std::endl;
}
DoubleCheckedSingleton(const DoubleCheckedSingleton&) = delete;
DoubleCheckedSingleton& operator=(const DoubleCheckedSingleton&) = delete;
public:
static DoubleCheckedSingleton* getInstance() {
DoubleCheckedSingleton* tmp = instance.load(std::memory_order_acquire);
if (tmp == nullptr) { // 第一次检查,避免不必要的同步
std::lock_guard<std::mutex> lock(mtx);
tmp = instance.load(std::memory_order_relaxed);
if (tmp == nullptr) { // 第二次检查,确保线程安全
tmp = new DoubleCheckedSingleton();
instance.store(tmp, std::memory_order_release);
}
}
return tmp;
}
void showMessage() {
std::cout << "Hello from Double Checked Singleton!" << std::endl;
}
static void destroy() {
DoubleCheckedSingleton* tmp = instance.load(std::memory_order_acquire);
if (tmp != nullptr) {
delete tmp;
instance.store(nullptr, std::memory_order_release);
}
}
};
// 静态成员初始化
std::atomic<DoubleCheckedSingleton*> DoubleCheckedSingleton::instance{nullptr};
std::mutex DoubleCheckedSingleton::mtx;
2.6 Meyers' Singleton(推荐使用)
cpp
/**
* Meyers' Singleton - C++中最优雅的实现方式
* 兼顾懒加载和线程安全,C++11标准保证线程安全
*/
class MeyersSingleton {
private:
MeyersSingleton() {
std::cout << "Meyers单例实例被创建" << std::endl;
}
MeyersSingleton(const MeyersSingleton&) = delete;
MeyersSingleton& operator=(const MeyersSingleton&) = delete;
public:
static MeyersSingleton& getInstance() {
static MeyersSingleton instance; // C++11保证线程安全
return instance;
}
void showMessage() {
std::cout << "Hello from Meyers Singleton!" << std::endl;
}
};
// 使用示例
int main() {
MeyersSingleton& singleton1 = MeyersSingleton::getInstance();
MeyersSingleton& singleton2 = MeyersSingleton::getInstance();
singleton1.showMessage();
std::cout << "是否是同一个实例: " << (&singleton1 == &singleton2) << std::endl;
// 输出:1 (true)
return 0;
}
2.7 返回指针的Meyers' Singleton变体
cpp
/**
* 返回指针的Meyers' Singleton变体
* 适用于需要指针语义的场景
*/
class MeyersPointerSingleton {
private:
MeyersPointerSingleton() {
std::cout << "Meyers指针单例实例被创建" << std::endl;
}
MeyersPointerSingleton(const MeyersPointerSingleton&) = delete;
MeyersPointerSingleton& operator=(const MeyersPointerSingleton&) = delete;
public:
static MeyersPointerSingleton* getInstance() {
static MeyersPointerSingleton instance;
return &instance;
}
void showMessage() {
std::cout << "Hello from Meyers Pointer Singleton!" << std::endl;
}
};
第三章:C++单例模式的实现方式对比
3.1 各种实现方式比较
| 实现方式 | 懒加载 | 线程安全 | 性能 | 内存管理 | 推荐指数 |
|---|---|---|---|---|---|
| 饿汉式 | ❌ | ✅ | ⭐⭐⭐⭐⭐ | 自动 | ⭐⭐⭐ |
| 懒汉式(非线程安全) | ✅ | ❌ | ⭐⭐⭐⭐⭐ | 手动 | ❌ |
| 懒汉式(同步方法) | ✅ | ✅ | ⭐ | 手动 | ⭐ |
| 双重检查锁 | ✅ | ✅ | ⭐⭐⭐⭐ | 手动 | ⭐⭐⭐ |
| Meyers' Singleton | ✅ | ✅ | ⭐⭐⭐⭐⭐ | 自动 | ⭐⭐⭐⭐⭐ |
3.2 如何选择实现方式?
选择指南:
- C++11及以上:优先使用Meyers' Singleton
- 需要最大性能:饿汉式(如果不介意启动时创建)
- 旧版本C++:双重检查锁或手动同步的懒汉式
- 简单项目:饿汉式
第四章:实际项目中的应用场景
4.1 配置文件管理器
cpp
#include <iostream>
#include <string>
#include <unordered_map>
/**
* 配置文件管理器 - 单例模式典型应用
*/
class ConfigManager {
private:
static ConfigManager* instance;
static std::mutex mtx;
std::unordered_map<std::string, std::string> config;
ConfigManager() {
// 加载默认配置或从文件读取
config["database.host"] = "localhost";
config["database.port"] = "3306";
config["app.name"] = "MyApp";
std::cout << "配置管理器初始化完成" << std::endl;
}
ConfigManager(const ConfigManager&) = delete;
ConfigManager& operator=(const ConfigManager&) = delete;
public:
static ConfigManager& getInstance() {
std::lock_guard<std::mutex> lock(mtx);
if (instance == nullptr) {
instance = new ConfigManager();
}
return *instance;
}
std::string getConfig(const std::string& key) {
auto it = config.find(key);
return it != config.end() ? it->second : "";
}
std::string getConfig(const std::string& key, const std::string& defaultValue) {
auto it = config.find(key);
return it != config.end() ? it->second : defaultValue;
}
void setConfig(const std::string& key, const std::string& value) {
config[key] = value;
}
static void destroy() {
if (instance != nullptr) {
delete instance;
instance = nullptr;
}
}
};
// 静态成员初始化
ConfigManager* ConfigManager::instance = nullptr;
std::mutex ConfigManager::mtx;
4.2 日志记录器
cpp
#include <fstream>
#include <iostream>
#include <chrono>
#include <iomanip>
/**
* 日志记录器 - 单例模式应用
*/
class Logger {
private:
static Logger instance;
std::ofstream logFile;
Logger() {
logFile.open("app.log", std::ios::app);
if (!logFile.is_open()) {
std::cerr << "无法打开日志文件!" << std::endl;
}
}
Logger(const Logger&) = delete;
Logger& operator=(const Logger&) = delete;
public:
static Logger& getInstance() {
return instance;
}
void log(const std::string& message) {
if (logFile.is_open()) {
auto now = std::chrono::system_clock::now();
auto time_t = std::chrono::system_clock::to_time_t(now);
logFile << std::put_time(std::localtime(&time_t), "%Y-%m-%d %H:%M:%S");
logFile << ": " << message << std::endl;
}
}
~Logger() {
if (logFile.is_open()) {
logFile.close();
}
}
};
// 静态成员初始化
Logger Logger::instance;
4.3 缓存管理器
cpp
#include <unordered_map>
#include <string>
#include <memory>
#include <mutex>
/**
* 缓存管理器 - 单例模式应用
*/
template<typename K, typename V>
class CacheManager {
private:
static std::unique_ptr<CacheManager> instance;
static std::mutex mtx;
std::unordered_map<K, V> cache;
CacheManager() = default;
CacheManager(const CacheManager&) = delete;
CacheManager& operator=(const CacheManager&) = delete;
public:
static CacheManager& getInstance() {
std::lock_guard<std::mutex> lock(mtx);
if (!instance) {
instance = std::unique_ptr<CacheManager>(new CacheManager());
}
return *instance;
}
void put(const K& key, const V& value) {
std::lock_guard<std::mutex> lock(mtx);
cache[key] = value;
}
V get(const K& key) {
std::lock_guard<std::mutex> lock(mtx);
auto it = cache.find(key);
return it != cache.end() ? it->second : V();
}
bool contains(const K& key) {
std::lock_guard<std::mutex> lock(mtx);
return cache.find(key) != cache.end();
}
void remove(const K& key) {
std::lock_guard<std::mutex> lock(mtx);
cache.erase(key);
}
void clear() {
std::lock_guard<std::mutex> lock(mtx);
cache.clear();
}
};
// 静态成员初始化
template<typename K, typename V>
std::unique_ptr<CacheManager<K, V>> CacheManager<K, V>::instance = nullptr;
template<typename K, typename V>
std::mutex CacheManager<K, V>::mtx;
第五章:C++单例模式的特有问题和解决方案
5.1 静态初始化顺序问题
cpp
// 解决方案:使用局部静态变量(Meyers' Singleton)
class DependencyManager {
public:
static DependencyManager& getInstance() {
static DependencyManager instance;
return instance;
}
};
class MainSystem {
public:
static MainSystem& getInstance() {
static MainSystem instance;
return instance;
}
void initialize() {
// 保证DependencyManager先初始化
DependencyManager::getInstance();
}
};
5.2 单例的依赖关系管理
cpp
class DatabaseManager {
public:
static DatabaseManager& getInstance() {
static DatabaseManager instance;
return instance;
}
void connect() {
std::cout << "数据库连接建立" << std::endl;
}
};
class ServiceManager {
public:
static ServiceManager& getInstance() {
static ServiceManager instance;
instance.initialize(); // 确保依赖的单例先初始化
return instance;
}
private:
void initialize() {
DatabaseManager::getInstance().connect();
}
};
第六章:单例模式的优缺点
6.1 优点 ✅
-
严格控制实例数量
- 确保一个类只有一个实例
- 避免资源浪费和冲突
-
全局访问点
- 提供统一的访问入口
- 方便管理和维护
-
节省系统资源
- 避免重复创建对象
- 提高性能
-
延迟初始化
- 可以等到真正需要时才创建实例
6.2 缺点 ❌
-
违反单一职责原则
- 单例类既负责业务逻辑,又控制实例数量
-
难以测试
- 全局状态使得单元测试困难
- 难以模拟和替换
-
隐藏的依赖关系
- 单例的使用在代码中不明显
- 增加代码的耦合度
-
可能成为上帝对象
- 单例类可能承担过多职责
第七章:最佳实践和常见陷阱
7.1 最佳实践 ✅
-
优先使用Meyers' Singleton
cpp// 推荐:Meyers' Singleton class BestSingleton { public: static BestSingleton& getInstance() { static BestSingleton instance; return instance; } private: BestSingleton() = default; }; -
使用智能指针管理资源
cppclass SmartPointerSingleton { private: static std::unique_ptr<SmartPointerSingleton> instance; public: static SmartPointerSingleton& getInstance() { if (!instance) { instance = std::make_unique<SmartPointerSingleton>(); } return *instance; } }; -
考虑使用依赖注入替代
cpp// 使用依赖注入框架替代手动单例 class UserService { private: std::shared_ptr<ConfigManager> config; public: UserService(std::shared_ptr<ConfigManager> config) : config(config) {} };
7.2 常见陷阱 ❌
-
忘记防止拷贝和赋值
cpp// 错误:没有防止拷贝 class BadSingleton { public: static BadSingleton& getInstance() { static BadSingleton instance; return instance; } // 缺少拷贝构造和赋值操作的删除声明 }; -
在多线程环境中使用非线程安全的单例
cpp// 错误:非线程安全 class UnsafeSingleton { public: static UnsafeSingleton* getInstance() { if (instance == nullptr) { // 竞态条件! instance = new UnsafeSingleton(); } return instance; } }; -
单例中保存非线程安全的状态
cpp// 错误:非线程安全的状态 class StatefulSingleton { private: std::vector<std::string> data; // 需要同步! public: void addData(const std::string& item) { data.push_back(item); // 多线程环境下可能出问题 } };
第八章:现代C++中的单例模式替代方案
8.1 使用依赖注入框架
cpp
// 使用现代C++的依赖注入理念
class ServiceLocator {
private:
static std::unordered_map<std::type_index, std::shared_ptr<void>> services;
static std::mutex mtx;
public:
template<typename T>
static void registerService(std::shared_ptr<T> service) {
std::lock_guard<std::mutex> lock(mtx);
services[std::type_index(typeid(T))] = service;
}
template<typename T>
static std::shared_ptr<T> getService() {
std::lock_guard<std::mutex> lock(mtx);
auto it = services.find(std::type_index(typeid(T)));
return it != services.end() ? std::static_pointer_cast<T>(it->second) : nullptr;
}
};
8.2 使用命名空间和静态函数
cpp
// 如果不需要状态,使用命名空间
namespace MathUtils {
static int add(int a, int b) {
return a + b;
}
static int multiply(int a, int b) {
return a * b;
}
}
第九章:实战练习
练习1:实现一个线程安全的配置管理器
要求支持热重载配置,且保证线程安全。
练习2:实现一个数据库连接池单例
要求支持连接复用和连接数控制。
练习3:实现一个支持模板的单例基类
要求能够通过继承快速创建单例类。
练习4:使用Meyers' Singleton实现一个日志记录器
要求支持不同的日志级别和输出目标。
总结
C++单例模式的核心价值在于控制实例数量 和提供全局访问点。通过本教程的学习,你应该能够:
- 理解C++中各种单例实现方式及其适用场景
- 掌握线程安全的单例实现技巧
- 避免单例模式的常见陷阱和误用
- 在实际项目中正确选择和使用单例模式
核心要点回顾:
- 唯一性:确保一个类只有一个实例
- 全局访问:提供统一的访问入口
- 线程安全:多线程环境下的正确性
- 合理使用:不要滥用单例模式
💡 小贴士: 在C++中,Meyers' Singleton是大多数情况下的最佳选择,它简洁、安全且高效。只有在特殊需求(如需要指针语义、需要显式生命周期控制)时才考虑其他实现方式。
记住:单例模式是一把双刃剑,用得好能提高效率,用不好会制造麻烦!
现在就在你的C++项目中尝试实现一个安全的单例模式吧!比如为你的应用创建一个配置管理器。Happy Coding! 🚀