C++设计模式之单例模式:以小区快递柜为例

在日常生活中,我们总会遇到"独一无二"的物品------比如小区门口的快递柜。整个小区里,不管是业主、快递员还是物业,使用的都是同一组快递柜,不会因为张三要存快递就新增一套,李四取快递再建一套。这种"全局唯一实例、统一访问"的场景,在编程世界里就对应着一种经典设计模式------单例模式。

单例模式的核心定义是:确保一个类在整个程序运行过程中只存在一个实例对象,并且提供一个全局唯一的访问接口来获取这个实例,避免因重复创建实例导致的资源浪费(如内存占用、数据库连接冲突等)。就像快递柜,统一管理才能避免快递混乱存放,单例模式也能让类的实例"有序可控"。

一、单例模式的核心要点(对应快递柜的特性)

  • 唯一实例:小区只有一套快递柜,类只有一个实例。这就要求我们禁止外部通过"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后通过局部静态变量可轻松实现线程安全。实际开发中,需根据"是否需要线程安全""实例是否必须提前创建"等需求选择合适的实现方式,避免过度使用单例模式(如后续扩展多个实例时会增加修改成本)。

相关推荐
AM越.2 小时前
Java设计模式超详解——抽象工厂设计模式(含UML图)
java·设计模式·uml
职业码农NO.12 小时前
智能体AI的六大核心设计模式,很常见
人工智能·设计模式·系统架构·aigc·rag
蜗牛love天空2 小时前
qt窗口机制和mfc窗口机制
c++
小鱼小鱼.oO2 小时前
Agent 设计模式
设计模式
定义小花2 小时前
c++ cmake qt
开发语言·c++·qt
草莓熊Lotso2 小时前
哈希表的两种灵魂:深入探索开放定址与链地址法的核心机密
linux·运维·数据结构·c++·人工智能·算法·哈希算法
赖small强2 小时前
【Linux C/C++开发】Linux C/C++编译参数 `-fPIC` 深度解析
linux·c语言·c++
小年糕是糕手2 小时前
【C++】内存管理(上)
java·开发语言·jvm·c++·算法·spring·servlet
csdn_aspnet2 小时前
C++ 长方体表面积和体积计算程序(Program for Surface Area and Volume of Cuboid)
c++