在日常生活中,我们总会遇到"独一无二"的物品------比如小区门口的快递柜。整个小区里,不管是业主、快递员还是物业,使用的都是同一组快递柜,不会因为张三要存快递就新增一套,李四取快递再建一套。这种"全局唯一实例、统一访问"的场景,在编程世界里就对应着一种经典设计模式------单例模式。
单例模式的核心定义是:确保一个类在整个程序运行过程中只存在一个实例对象,并且提供一个全局唯一的访问接口来获取这个实例,避免因重复创建实例导致的资源浪费(如内存占用、数据库连接冲突等)。就像快递柜,统一管理才能避免快递混乱存放,单例模式也能让类的实例"有序可控"。
一、单例模式的核心要点(对应快递柜的特性)
-
唯一实例:小区只有一套快递柜,类只有一个实例。这就要求我们禁止外部通过"new"关键字随意创建类的对象,必须通过类自身提供的接口获取实例。
-
私有构造函数:快递柜不会自己"复制"出另一套,类的构造函数必须设为私有,阻止外部直接创建实例。同时,拷贝构造函数和赋值运算符也需禁用,避免通过拷贝生成新实例。
-
全局访问接口:业主通过快递柜的"操作面板"存取快递,类需提供一个静态的公有成员函数(如GetInstance()),作为全局访问入口,返回唯一的实例对象。
-
线程安全(可选但重要):如果多个快递员同时存快递,快递柜要能正常工作;如果程序是多线程的,创建实例时要避免因并发导致创建多个实例,需做好线程同步。
二、C++单例模式的两种经典实现(附代码示例)
结合快递柜的场景,我们以"CommunityExpressCabinet"(小区快递柜类)为例,实现两种常用的单例模式:饿汉式和懒汉式。
1. 饿汉式单例:快递柜"提前建好"
饿汉式的核心思路是:在程序启动时(类加载阶段)就主动创建唯一的实例对象,之后全局访问时直接返回该实例。就像小区在业主入住前,就提前建好快递柜,随时可以使用。
优点:实现简单,无需考虑线程安全问题(实例在程序启动时单线程创建);缺点:如果程序全程不使用该实例,会造成内存浪费(相当于快递柜建好后一直没人用)。
cpp
#include <iostream>
#include <string>
using namespace std;
// 小区快递柜类(饿汉式单例)
class CommunityExpressCabinet {
private:
// 1. 私有构造函数:禁止外部创建实例
CommunityExpressCabinet() {
cout << "小区快递柜已建成,开始服务!" << endl;
}
// 2. 禁用拷贝构造和赋值运算符:避免复制实例
CommunityExpressCabinet(const CommunityExpressCabinet&) = delete;
CommunityExpressCabinet& operator=(const CommunityExpressCabinet&) = delete;
// 3. 私有静态实例:程序启动时创建,全局唯一
static CommunityExpressCabinet instance;
// 快递柜的模拟数据:存储快递(柜号-快递信息)
string cabinets[10] = {""}; // 假设10个柜格
public:
// 4. 公有静态访问接口:全局唯一获取实例的方式
static CommunityExpressCabinet& GetInstance() {
return instance;
}
// 快递柜的业务方法:存快递
bool StoreExpress(int cabinetNum, const string& expressInfo) {
if (cabinetNum < 0 || cabinetNum > 9) {
cout << "柜号无效!请输入0-9之间的柜号。" << endl;
return false;
}
if (cabinets[cabinetNum] != "") {
cout << "柜格" << cabinetNum << "已占用,存件失败!" << endl;
return false;
}
cabinets[cabinetNum] = expressInfo;
cout << "快递【" << expressInfo << "】已存入柜格" << cabinetNum << endl;
return true;
}
// 快递柜的业务方法:取快递
bool TakeExpress(int cabinetNum, string& expressInfo) {
if (cabinetNum < 0 || cabinetNum > 9) {
cout << "柜号无效!请输入0-9之间的柜号。" << endl;
return false;
}
if (cabinets[cabinetNum] == "") {
cout << "柜格" << cabinetNum << "为空,取件失败!" << endl;
return false;
}
expressInfo = cabinets[cabinetNum];
cabinets[cabinetNum] = ""; // 取件后清空柜格
cout << "从柜格" << cabinetNum << "取出快递:" << expressInfo << endl;
return true;
}
};
// 关键:在类外部初始化静态实例(程序启动时执行)
CommunityExpressCabinet CommunityExpressCabinet::instance;
// 测试代码
int main() {
// 1. 获取快递柜实例(两次获取的是同一个实例)
CommunityExpressCabinet& cabinet1 = CommunityExpressCabinet::GetInstance();
CommunityExpressCabinet& cabinet2 = CommunityExpressCabinet::GetInstance();
// 2. 验证是否为同一个实例(地址相同)
cout << "柜1地址:" << &cabinet1 << endl;
cout << "柜2地址:" << &cabinet2 << endl;
// 3. 存快递操作
cabinet1.StoreExpress(0, "张三的手机壳");
cabinet2.StoreExpress(0, "李四的图书"); // 同一柜格,存件失败
cabinet2.StoreExpress(1, "李四的图书");
// 4. 取快递操作
string express;
cabinet1.TakeExpress(1, express);
cabinet2.TakeExpress(1, express); // 已取出,取件失败
// 5. 禁止创建新实例(编译报错)
// CommunityExpressCabinet cabinet3; // 错误:构造函数私有
// CommunityExpressCabinet cabinet4 = cabinet1; // 错误:拷贝构造已禁用
return 0;
}
结果分析:两次获取的实例地址相同,证明是唯一实例;存件时同一柜格会冲突,取件后柜格清空,符合快递柜的实际逻辑。
2. 懒汉式单例:快递柜"按需建成"
懒汉式的核心思路是:程序启动时不创建实例,直到第一次调用访问接口(GetInstance())时,才创建唯一的实例。就像小区先规划好快递柜位置,等第一个业主需要存快递时再建好,避免资源浪费。
优点:按需创建实例,节省内存;缺点:需要处理线程安全问题(多线程同时调用GetInstance()可能创建多个实例),C++11后可通过"局部静态变量"简化实现并保证线程安全。
cpp
#include <iostream>
#include <string>
#include <thread>
#include <chrono>
using namespace std;
// 小区快递柜类(懒汉式单例,C++11线程安全版)
class CommunityExpressCabinet {
private:
// 1. 私有构造函数
CommunityExpressCabinet() {
// 模拟快递柜建造耗时(验证线程安全)
this_thread::sleep_for(chrono::milliseconds(500));
cout << "小区快递柜已建成,开始服务!" << endl;
}
// 2. 禁用拷贝构造和赋值运算符
CommunityExpressCabinet(const CommunityExpressCabinet&) = delete;
CommunityExpressCabinet& operator=(const CommunityExpressCabinet&) = delete;
// 快递柜模拟数据
string cabinets[10] = {""};
public:
// 3. 公有静态访问接口:C++11中局部静态变量初始化是线程安全的
static CommunityExpressCabinet& GetInstance() {
// 第一次调用时创建实例,之后直接返回
static CommunityExpressCabinet instance;
return instance;
}
// 存快递方法(同饿汉式)
bool StoreExpress(int cabinetNum, const string& expressInfo) {
if (cabinetNum < 0 || cabinetNum > 9) {
cout << "柜号无效!请输入0-9之间的柜号。" << endl;
return false;
}
if (cabinets[cabinetNum] != "") {
cout << "柜格" << cabinetNum << "已占用,存件失败!" << endl;
return false;
}
cabinets[cabinetNum] = expressInfo;
cout << "快递【" << expressInfo << "】已存入柜格" << cabinetNum << endl;
return true;
}
// 取快递方法(同饿汉式)
bool TakeExpress(int cabinetNum, string& expressInfo) {
if (cabinetNum < 0 || cabinetNum > 9) {
cout << "柜号无效!请输入0-9之间的柜号。" << endl;
return false;
}
if (cabinets[cabinetNum] == "") {
cout << "柜格" << cabinetNum << "为空,取件失败!" << endl;
return false;
}
expressInfo = cabinets[cabinetNum];
cabinets[cabinetNum] = "";
cout << "从柜格" << cabinetNum << "取出快递:" << expressInfo << endl;
return true;
}
};
// 测试多线程场景(验证线程安全)
void TestSingletonInThread(int threadId) {
cout << "线程" << threadId << "尝试获取快递柜实例..." << endl;
CommunityExpressCabinet& cabinet = CommunityExpressCabinet::GetInstance();
cout << "线程" << threadId << "获取的实例地址:" << &cabinet << endl;
}
// 主函数
int main() {
cout << "程序启动,未创建快递柜实例..." << endl;
this_thread::sleep_for(chrono::seconds(1)); // 等待1秒,观察是否提前创建
// 多线程同时获取实例(验证线程安全)
thread t1(TestSingletonInThread, 1);
thread t2(TestSingletonInThread, 2);
thread t3(TestSingletonInThread, 3);
t1.join();
t2.join();
t3.join();
// 业务操作
CommunityExpressCabinet& cabinet = CommunityExpressCabinet::GetInstance();
cabinet.StoreExpress(5, "王五的零食");
string express;
cabinet.TakeExpress(5, express);
return 0;
}
结果分析:程序启动后并未立即创建实例,直到线程调用GetInstance()时才创建;三个线程获取的实例地址相同,证明C++11的局部静态变量实现了线程安全的懒汉式单例。
三、单例模式的适用场景(对应生活中的"唯一资源")
-
资源共享场景:如日志管理器(整个程序只有一个日志文件写入实例)、配置管理器(配置文件只加载一次),类似快递柜统一管理小区快递。
-
控制唯一实例场景:如打印机后台处理程序(同一时间只能有一个打印任务调度)、数据库连接池(避免创建过多连接浪费资源)。
-
避免重复创建场景:如界面中的"设置对话框",每次打开都是同一个对话框实例,不会重复创建多个窗口。
四、总结
单例模式的核心是"唯一实例+全局访问",就像小区的快递柜,通过"私有构造阻止重复创建"和"静态接口统一访问"保证唯一性。在C++中,饿汉式实现简单但可能浪费资源,适合实例必须提前初始化的场景;懒汉式按需创建更高效,C++11后通过局部静态变量可轻松实现线程安全。实际开发中,需根据"是否需要线程安全""实例是否必须提前创建"等需求选择合适的实现方式,避免过度使用单例模式(如后续扩展多个实例时会增加修改成本)。