网站首页 > 技术文章 正文
单例模式:确保唯一实例
单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点。 它是最简单和最常用的设计模式之一,本文介绍C语言及C++实现的3种单例模式代码。
目的
单例模式的主要目的是控制对象创建,将实例数量限制为一个。 当只需要一个对象来协调整个系统的操作时,这非常有用。
应用场景
单例模式适用于各种场景,包括:
- 日志记录: 可以在整个应用程序中使用单个日志记录对象来将消息写入文件或控制台。
- 配置管理: 单个配置对象可以存储应用程序设置,为所有组件提供一个中心访问点。
- 数据库连接: 可以在应用程序的多个部分共享单个数据库连接对象,以避免资源耗尽。
- 打印后台处理程序: 在操作系统中,单个打印后台处理程序对象管理打印任务。
- 缓存: 单个缓存对象可以存储经常访问的数据,从而提高性能。
优点
- 受控访问: 单例模式提供对单个实例的受控访问。
- 减少命名空间污染: 它避免了用不必要的对象污染全局命名空间。
- 资源效率: 它可以确保只创建一个资源密集型对象的实例,从而节省资源。
- 灵活性: 它可以轻松扩展或修改,而不会影响客户端。
缺点
- 难以测试: 单例模式会引入全局状态,因此可能难以测试。
- 潜在的紧密耦合: 过度使用单例模式可能导致类之间的紧密耦合。
- 并发问题: 在多线程环境中,必须特别注意确保线程安全。
- 违反单一职责原则: 单例类既负责其自身的逻辑,又负责确保其单例实例。
C 语言实现
以下是在 C 语言中实现单例模式的示例:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
typedef struct {
int data;
} Singleton;
Singleton *instance = NULL;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
Singleton *getInstance() {
if (instance == NULL) {
pthread_mutex_lock(&mutex);
if (instance == NULL) {
instance = (Singleton*)malloc(sizeof(Singleton));
instance->data = 0; // 初始化数据
}
pthread_mutex_unlock(&mutex);
}
return instance;
}
void destroyInstance() {
pthread_mutex_lock(&mutex);
if (instance != NULL) {
free(instance);
instance = NULL;
}
pthread_mutex_unlock(&mutex);
}
int main() {
Singleton *s1 = getInstance();
Singleton *s2 = getInstance();
s1->data = 10;
printf("s1 data: %d\n", s1->data);
printf("s2 data: %d\n", s2->data); // s2 也会打印 10,因为它是同一个实例
destroyInstance(); // 清理实例
return 0;
}
C++实现
方法一:Meyers' Singleton (C++11 及以上推荐)
这是目前最推荐、最简洁且线程安全(自 C++11 起)的实现方式。它利用了函数内部静态变量的特性:静态局部变量在第一次执行到其声明时被初始化,并且这种初始化是线程安全的。
#include <iostream>
#include <thread> // 用于演示线程安全
#include <vector>
class Singleton {
public:
// 1. 提供一个公共的静态方法来获取唯一的实例
static Singleton& GetInstance() {
// 2. 使用静态局部变量。C++11保证其初始化是线程安全的。
// 实例将在第一次调用 GetInstance() 时创建。
static Singleton instance;
return instance;
}
// 3. 删除拷贝构造函数和拷贝赋值运算符,防止复制实例
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
// 4. (可选)删除移动构造函数和移动赋值运算符
Singleton(Singleton&&) = delete;
Singleton& operator=(Singleton&&) = delete;
// 示例方法
void DoSomething() {
std::cout << "Singleton instance (" << this << ") is doing something." << std::endl;
}
// 示例数据
int GetData() const {
return data_;
}
void SetData(int data) {
data_ = data;
}
private:
// 5. 将构造函数设为私有(或保护),防止外部直接创建实例
Singleton() : data_(0) {
std::cout << "Singleton instance created at address: " << this << std::endl;
}
// 6. (可选)将析构函数设为私有或保护,如果需要控制销毁时机
// 对于静态局部变量,它会在程序结束时自动销毁,通常不需要私有化
~Singleton() {
std::cout << "Singleton instance destroyed." << std::endl;
}
// 示例成员变量
int data_;
};
// --- 演示 ---
void ThreadTask() {
// 从不同线程获取实例
Singleton& instance = Singleton::GetInstance();
std::cout << "Thread " << std::this_thread::get_id()
<< " got instance at address: " << &instance << std::endl;
instance.DoSomething();
}
int main() {
std::cout << "--- Basic Usage ---" << std::endl;
Singleton& instance1 = Singleton::GetInstance();
instance1.SetData(100);
instance1.DoSomething();
Singleton& instance2 = Singleton::GetInstance();
instance2.DoSomething(); // 注意地址与 instance1 相同
std::cout << "Data from instance2: " << instance2.GetData() << std::endl; // 输出 100
std::cout << "\n--- Thread Safety Demo ---" << std::endl;
std::vector<std::thread> threads;
for (int i = 0; i < 5; ++i) {
threads.emplace_back(ThreadTask);
}
for (auto& t : threads) {
t.join();
}
std::cout << "\nProgram finished." << std::endl;
// instance 会在 main 函数结束后自动销毁
return 0;
}
Meyers' Singleton 优点:
- 延迟初始化(Lazy Initialization): 实例仅在第一次调用 GetInstance() 时创建。
- 线程安全(Thread-Safe): C++11 标准保证了静态局部变量的初始化过程是线程安全的,无需手动加锁。
- 简洁: 代码实现非常简单。
- 自动销毁: 实例会在程序结束时自动销毁,通常不需要手动管理生命周期。
方法二:饿汉式(Eager Initialization)
这种方式在程序启动时就创建实例。
#include <iostream>
class EagerSingleton {
public:
// 1. 提供公共静态方法获取实例
static EagerSingleton& GetInstance() {
return instance_; // 直接返回已创建的实例
}
// 2. 删除拷贝和移动操作
EagerSingleton(const EagerSingleton&) = delete;
EagerSingleton& operator=(const EagerSingleton&) = delete;
EagerSingleton(EagerSingleton&&) = delete;
EagerSingleton& operator=(EagerSingleton&&) = delete;
void DoSomething() {
std::cout << "EagerSingleton instance (" << this << ") is doing something." << std::endl;
}
private:
// 3. 私有构造函数
EagerSingleton() {
std::cout << "EagerSingleton instance created." << std::endl;
}
~EagerSingleton() {
std::cout << "EagerSingleton instance destroyed." << std::endl;
}
// 4. 在类外初始化静态成员实例
// 这会在程序加载时(main函数执行前)创建实例
static EagerSingleton instance_;
};
// 初始化静态成员变量
EagerSingleton EagerSingleton::instance_;
int main() {
EagerSingleton& s1 = EagerSingleton::GetInstance();
s1.DoSomething();
EagerSingleton& s2 = EagerSingleton::GetInstance();
s2.DoSomething(); // 地址与 s1 相同
return 0;
}
饿汉式 优点:
- 线程安全: 实例在单线程的程序启动阶段创建,初始化本身没有线程安全问题。
- 简单: 实现也比较简单。
饿汉式 缺点:
- 非延迟加载: 即使程序从未使用该实例,它也会在启动时被创建,可能造成资源浪费或启动速度变慢。
- 初始化顺序问题: 如果 EagerSingleton 的构造函数依赖于其他静态初始化的资源,可能会遇到静态初始化顺序问题(Static Initialization Order Fiasco)。
方法三:懒汉式(Lazy Initialization with Mutex - C++11 之前常见,现在不推荐)
在 C++11 之前,实现线程安全的懒汉式单例通常需要手动加锁。现代 C++ 中更推荐 Meyers' Singleton。这里仅作演示,展示其复杂性。
#include <iostream>
#include <mutex> // 需要互斥锁
#include <atomic> // 用于原子指针或标志
#include <memory> // 用于智能指针管理内存 (可选但推荐)
class LazyThreadSafeSingleton {
public:
// 1. 获取实例的方法
static LazyThreadSafeSingleton* GetInstance() {
// Double-Checked Locking Pattern (DCLP)
LazyThreadSafeSingleton* tmp = instance_.load(std::memory_order_acquire);
if (tmp == nullptr) { // 第一次检查(无锁)
std::lock_guard<std::mutex> lock(mutex_);
tmp = instance_.load(std::memory_order_relaxed); // 第二次检查(有锁)
if (tmp == nullptr) {
tmp = new LazyThreadSafeSingleton();
instance_.store(tmp, std::memory_order_release);
// (可选) 注册销毁函数
// std::atexit(DestroyInstance); // 如果不用智能指针,需要手动管理销毁
}
}
return tmp;
}
// 2. 删除拷贝和移动
LazyThreadSafeSingleton(const LazyThreadSafeSingleton&) = delete;
LazyThreadSafeSingleton& operator=(const LazyThreadSafeSingleton&) = delete;
LazyThreadSafeSingleton(LazyThreadSafeSingleton&&) = delete;
LazyThreadSafeSingleton& operator=(LazyThreadSafeSingleton&&) = delete;
void DoSomething() {
std::cout << "LazyThreadSafeSingleton instance (" << this << ") is doing something." << std::endl;
}
private:
// 3. 私有构造函数
LazyThreadSafeSingleton() {
std::cout << "LazyThreadSafeSingleton instance created." << std::endl;
}
~LazyThreadSafeSingleton() {
std::cout << "LazyThreadSafeSingleton instance destroyed." << std::endl;
}
// 4. 静态成员指针(使用原子类型保证可见性)
static std::atomic<LazyThreadSafeSingleton*> instance_;
// 5. 静态互斥锁,保护初始化过程
static std::mutex mutex_;
// (可选) 静态销毁方法,如果使用裸指针
// static void DestroyInstance() {
// LazyThreadSafeSingleton* tmp = instance_.load(std::memory_order_acquire);
// if (tmp != nullptr) {
// delete tmp;
// instance_.store(nullptr, std::memory_order_release);
// }
// }
};
// 初始化静态成员
std::atomic<LazyThreadSafeSingleton*> LazyThreadSafeSingleton::instance_{nullptr};
std::mutex LazyThreadSafeSingleton::mutex_;
// 注意:使用裸指针的版本需要手动管理内存,容易出错(例如忘记销毁、重复销毁、程序异常退出未销毁)。
// 使用智能指针(如 std::unique_ptr 配合 DCLP)会更安全,但实现更复杂。
// 这就是为什么 Meyers' Singleton 更受欢迎。
int main() {
LazyThreadSafeSingleton* s1 = LazyThreadSafeSingleton::GetInstance();
s1->DoSomething();
LazyThreadSafeSingleton* s2 = LazyThreadSafeSingleton::GetInstance();
s2->DoSomething(); // 地址与 s1 相同
// 需要注意:这个版本的实例是 new 出来的,如果不用智能指针或 atexit,
// 需要找到合适的时机 delete s1 (或 s2),否则会内存泄漏。
// delete s1; // 如果手动 delete,需要确保只 delete 一次,且后续不再访问。
// 正确处理销毁非常棘手,再次强调 Meyers' Singleton 的优势。
return 0;
}
懒汉式 (Mutex) 缺点:
- 复杂: 实现 DCLP(双重检查锁定模式)需要小心处理内存序(memory order)和锁,容易出错。
- 性能开销: 每次获取实例都需要进行至少一次原子读操作,如果实例未创建,还需要加锁,有性能损失。
- 内存管理: 如果使用裸指针,需要手动管理实例的销毁,容易导致内存泄漏或悬挂指针。
- 上一篇: 澳政府斥巨资开发新冠追踪APP:仅发现17例 还被曝收集隐私
- 下一篇: 车载科技警报防止孩子遗留车内
猜你喜欢
- 2025-05-28 iOS 14.7正式版发布:为什么叫FCS?为什么没有iPadOS?
- 2025-05-28 排忧解难 解决Windows 10安全模式三大问题
- 2025-05-28 1分钟速看!苹果新品发布会看点一览
- 2025-05-28 蔡健雅公司发声明否认新歌抄袭,要求停止造谣
- 2025-05-28 数据恢复工具
- 2025-05-28 车载科技警报防止孩子遗留车内
- 2025-05-28 澳政府斥巨资开发新冠追踪APP:仅发现17例 还被曝收集隐私
- 2025-05-28 分享一个彻底卸载360安全卫士的小技巧
- 2025-05-28 讨厌的2345“狗皮癣”弹窗如何彻底根除?
- 2025-05-28 滑板极限运动需要注意些什么呢
- 最近发表
-
- 使用这个新的 ECMAScript 运算符告别 Try/Catch!
- 抛弃 try-catch,错误处理的新方案
- 深圳尚学堂Java培训:总结java编程常用的快捷键(二)
- Try-catch speeding up my code?(speeding up)
- 能代替try catch处理异常的优雅方式
- Linux系统stress压力测试工具(linux自带的压力测试)
- ESL-通过事件控制FreeSWITCH(es事务控制)
- 谈JVM xmx, xms等内存相关参数合理性设置
- 嵌入式工程师竟然看不懂这些专业语句,那真别怪人说你菜
- 不会前端也能写官网?没问题,Devbox+Cursor 带你起飞
- 标签列表
-
- cmd/c (64)
- c++中::是什么意思 (83)
- 标签用于 (65)
- 主键只能有一个吗 (66)
- c#console.writeline不显示 (75)
- pythoncase语句 (81)
- es6includes (73)
- sqlset (64)
- windowsscripthost (67)
- apt-getinstall-y (86)
- node_modules怎么生成 (76)
- chromepost (65)
- c++int转char (75)
- static函数和普通函数 (76)
- el-date-picker开始日期早于结束日期 (70)
- localstorage.removeitem (74)
- vector线程安全吗 (70)
- & (66)
- java (73)
- js数组插入 (83)
- linux删除一个文件夹 (65)
- mac安装java (72)
- eacces (67)
- 查看mysql是否启动 (70)
- 无效的列索引 (74)