单例模式(C++)

单例模式

单例模式是一种常用的设计模式,其核心是确保一个类在全局只有唯一实例,并提供一个全局访问点。

一、核心原理

  1. 限制实例化:通过私有化类的构造函数、拷贝构造函数和赋值运算符,禁止外部直接创建实例或复制实例。
  2. 唯一实例:在类内部维护一个静态的自身实例指针,确保全局只有一个实例。
  3. 全局访问 :提供一个静态的公开接口(如 getInstance()),让外部通过该接口获取唯一实例。

二、常见的单例模式实现方式

1. 懒汉式(Lazy Initialization)

实例在第一次被使用时才创建(延迟初始化),节省资源。

cpp 复制代码
#include <QMutex>
#include <QScopedPointer>

class Singleton {
public:
    // 全局访问点:获取唯一实例
    static Singleton& getInstance() {
        // 双重检查锁定(DCLP),避免多线程下重复创建
        if (m_instance.isNull()) {
            QMutexLocker locker(&m_mutex); // 加锁,确保线程安全
            if (m_instance.isNull()) {
                m_instance.reset(new Singleton()); // 首次调用时创建实例
            }
        }
        return *m_instance;
    }

    // 示例:单例提供的功能方法
    void doSomething() {
        // ... 业务逻辑 ...
    }

private:
    // 私有化构造函数:禁止外部创建实例
    Singleton() {}

    // 私有化拷贝构造和赋值运算符:禁止复制
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    // 静态成员:存储唯一实例
    static QScopedPointer<Singleton> m_instance;
    static QMutex m_mutex; // 互斥锁,确保多线程安全
};

// 初始化静态成员(类外定义)
QScopedPointer<Singleton> Singleton::m_instance(nullptr);
QMutex Singleton::m_mutex;

2. 饿汉式(Eager Initialization)

实例在程序启动时(类加载时)就创建,避免多线程同步问题,但可能提前占用资源。

cpp 复制代码
class Singleton {
public:
    // 全局访问点:直接返回预创建的实例
    static Singleton& getInstance() {
        static Singleton instance; // 静态局部变量,程序启动时初始化
        return instance;
    }

    void doSomething() {
        // ... 业务逻辑 ...
    }

private:
    // 私有化构造函数
    Singleton() {}

    // 禁止复制
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
};

三、关键实现细节解析

  1. 私有化构造函数
    private 权限的构造函数阻止外部通过 new Singleton()Singleton obj 创建实例,确保实例只能在类内部创建。

  2. 禁止复制和赋值

    通过 = delete 显式删除拷贝构造函数和赋值运算符,避免外部通过 Singleton obj = Singleton::getInstance() 复制实例,保证唯一性。

  3. 静态实例与全局访问

    类内部的静态成员(m_instance 或静态局部变量)存储唯一实例,getInstance() 静态方法提供全局访问入口,确保任何地方都能获取同一个实例。

  4. 线程安全处理

    • 懒汉式中使用 QMutex 加锁,避免多线程同时调用 getInstance() 时创建多个实例(双重检查锁定进一步优化性能)。
    • 饿汉式依赖静态变量的初始化特性(C++11 后静态局部变量初始化是线程安全的),无需额外加锁。

四、单例模式的适用场景与特点

使用场景

  • 全局配置管理(如程序的配置类)。
  • 日志工具类(确保日志写入的唯一性)。
cpp 复制代码
//ErrorLogger.h文件
#ifndef ERRORLOGGER_H
#define ERRORLOGGER_H

#include <QString>
#include <QMutex>

// 错误日志工具类(单例模式)
class ErrorLogger
{
public:
    // 获取单例实例
    static ErrorLogger& getInstance();

    // 禁止拷贝和赋值
    ErrorLogger(const ErrorLogger&) = delete;
    ErrorLogger& operator=(const ErrorLogger&) = delete;

    // 写入错误日志
    void writeLog(const QString& errorMessage);

    // 设置日志文件路径(默认当前目录下的error.log)
    void setLogFilePath(const QString& path);

private:
    // 私有构造函数(单例模式)
    ErrorLogger();

    QString m_logFilePath; // 日志文件路径
    QMutex m_mutex;       // 互斥锁,确保多线程安全
};

#endif // ERRORLOGGER_H

//ErrorLogger.cpp文件
#include "ErrorLogger.h"
#include <QFile>
#include <QTextStream>
#include <QDateTime>
#include <QDir>
#include <QDebug>

ErrorLogger::ErrorLogger()
{
    // 默认日志路径:当前程序目录下的error.log
    m_logFilePath = QDir::currentPath() + "/error.log";
}

ErrorLogger& ErrorLogger::getInstance()
{
    static ErrorLogger instance;
    return instance;
}

void ErrorLogger::setLogFilePath(const QString& path)
{
    m_logFilePath = path;
}

void ErrorLogger::writeLog(const QString& errorMessage)
{
    // 多线程加锁,避免日志写入冲突
    QMutexLocker locker(&m_mutex);

    // 获取当前时间戳(格式:yyyy-MM-dd hh:mm:ss)
    QString timeStamp = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");

    // 构建日志内容(时间 + 错误信息)
    QString logContent = QString("[%1] Error: %2\n").arg(timeStamp).arg(errorMessage);

    // 打开文件(以追加模式,不存在则创建)
    QFile file(m_logFilePath);
    if (!file.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
        qDebug() << "无法写入日志文件:" << file.errorString();
        return;
    }

    // 写入日志
    QTextStream out(&file);
    out << logContent;
    file.close();
}

//main.cpp文件
// 写入错误日志(包含系统错误信息)
        ErrorLogger::getInstance().writeLog(
            QString("文件打开失败: %1(路径:%2)")
            .arg(file.errorString())
            .arg(file.fileName())
        );
// 可选:设置自定义日志路径(如程序数据目录)
    ErrorLogger::getInstance().setLogFilePath(
        QDir::homePath() + "/myapp/logs/error.log"
    );
  • 设备管理器(如硬件设备的唯一控制实例)。
  • 缓存管理器(避免重复创建缓存对象)。

特点

  • 优点

    确保全局唯一实例,减少资源消耗(如频繁创建销毁实例的开销),提供统一的访问点。

  • 缺点

    单例本质是全局变量,可能导致代码耦合度升高;测试困难(单例状态难以隔离);在多线程环境下需谨慎处理同步问题。

通过上述实现,单例模式能有效控制类的实例数量,在需要全局唯一访问点的场景中非常实用。

相关推荐
十五年专注C++开发2 分钟前
Asio2: 一个基于 Boost.Asio 封装的高性能网络编程库
网络·c++·boost·asio·asio2
gcfer4 分钟前
CS144 中的C++知识积累
c++·右值引用·智能指针·optional容器
局i18 分钟前
Vue 指令详解:v-for、v-if、v-show 与 {{}} 的妙用
前端·javascript·vue.js
Bona Sun1 小时前
单片机手搓掌上游戏机(二十)—pico运行doom之编译环境
c语言·c++·单片机·游戏机
꒰ঌ小武໒꒱1 小时前
RuoYi-Vue 前端环境搭建与部署完整教程
前端·javascript·vue.js·nginx
Albert Edison2 小时前
【项目设计】C++ 高并发内存池
数据结构·c++·单例模式·哈希算法·高并发
我真不会起名字啊2 小时前
C、C++中的sprintf和stringstream的使用
java·c语言·c++
猿饵块2 小时前
ros2--图像/image
c++
局i3 小时前
Vue 中 v-text 与 v-html 的区别:文本渲染与 HTML 解析的抉择
前端·javascript·vue.js
fruge3 小时前
接口 Mock 工具对比:Mock.js、Easy Mock、Apifox 的使用场景与配置
开发语言·javascript·ecmascript