单例模式【C++设计模式】

文章目录

单例模式

饿汉模式

  1. 将构造函数设置为私有,并将拷贝构造函数和赋值运算符重载函数设置为私有或删除,防止外部创建或拷贝对象。

  2. 提供一个指向单例对象的static指针,并在程序入口之前完成单例对象的初始化。

  3. 提供一个全局访问点获取单例对象

    class Singleton
    {
    public:
    //3、提供一个全局访问点获取单例对象
    static Singleton* GetInstance()
    {
    return _inst;
    }
    private:
    //1、将构造函数设置为私有,并防拷贝
    Singleton()
    {}
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
    //2、提供一个指向单例对象的static指针
    static Singleton* _inst;
    };

    //在程序入口之前完成单例对象的初始化
    Singleton* Singleton::_inst = new Singleton;

线程安全相关问题:

饿汉模式在程序运行主函数之前就完成了单例对象的创建,由于main函数之前是不存在多线程的,因此饿汉模式下单例对象的创建过程是线程安全的

后续所有多线程要访问这个单例对象,都需要通过调用GetInstance函数来获取,这个获取过程是不需要加锁的,因为这是一个读操作

当然,如果线程通过GetInstance获取到单例对象后,要用这个单例对象进行一些线程不安全的操作,那么这时就需要加锁了

例如:

复制代码
#include<map>
#include<string>
#include<iostream>
using namespace std;
class Singleton
{
public:
    //提供获取单例对象的接口函数
    static Singleton& GetInstance()
    {
        return _sinst;
    }



    void func();
    void Add(const pair<string, string>& kv)
    {
        _dict[kv.first] = kv.second;
    }

    void Print()
    {

        for (auto& e : _dict)
        {
            cout << e.first << ":" << e.second << endl;
        }
        cout << endl;
    }


private:
    // 1、构造函数私有
    Singleton()
    {
        // ...
    }

    // 3、防拷贝
    Singleton(const Singleton& s) = delete;
    Singleton& operator=(const Singleton& s) = delete;

    map<string, string> _dict;
    // 静态实例
    static Singleton _sinst;

};

Singleton   Singleton::_sinst;


int main()
{
    //Singleton::GetInstance();
    cout << &Singleton::GetInstance() << endl;
    cout << &Singleton::GetInstance() << endl;
    cout << &Singleton::GetInstance() << endl;

    // Singleton copy(Singleton::GetInstance());



    Singleton::GetInstance().Add({ "xxx", "111" });
    Singleton::GetInstance().Add({ "yyy", "222" });
    Singleton::GetInstance().Add({ "zzzzz", "333" });
    Singleton::GetInstance().Add({ "abc", "333" });
    Singleton::GetInstance().Print();
}

懒汉模式

  1. 将构造函数设置为私有,并将拷贝构造函数和赋值运算符重载函数设置为私有或删除,防止外部创建或拷贝对象。

  2. 提供一个指向单例对象的static指针,并在程序入口之前先将其初始化为空。

  3. 提供一个全局访问点获取单例对象

    class Singleton
    {
    public:
    //3、提供一个全局访问点获取单例对象
    static Singleton* GetInstance()
    {
    //双检查
    if (_inst == nullptr)
    {
    _mtx.lock();
    if (_inst == nullptr)
    {
    _inst = new Singleton;
    }
    _mtx.unlock();
    }
    return _inst;
    }
    private:
    //1、将构造函数设置为私有,并防拷贝
    Singleton()
    {}
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
    //2、提供一个指向单例对象的static指针
    static Singleton* _inst;
    static mutex _mtx; //互斥锁
    };

    //在程序入口之前先将static指针初始化为空
    Singleton* Singleton::_inst = nullptr;
    mutex Singleton::_mtx; //初始化互斥锁

饿汉模式的优点就是简单,但是它的缺点也比较明显。饿汉模式在程序运行主函数之前就会创建单例对象,如果单例类的构造函数中所做的工作比较多,就会导致程序迟迟无法进入主函数,在外部看来就好像是程序卡住了。

此外,如果有多个单例类需要创建单例对象,并且它们之间的初始化存在某种依赖关系,比如单例对象A的创建必须在单例对象B之后,此时饿汉模式也会存在问题,因为我们无法保证这多个单例对象中的哪个对象先创建

而懒汉模式就能很好的解决上述饿汉模式的缺点,因为懒汉模式并不是一开始就完成单例对象的创建,因此不会导致程序迟迟无法进入主函数,并且懒汉模式中各个单例对象创建的顺序是由各个单例类中的GetInstance函数第一次被调用的顺序决定,因此是可控制的。

懒汉模式的缺点就是,在编码上比饿汉模式复杂,在创建单例对象时需要考虑线程安全的问题

单例场景

在 RPC(Remote Procedure Call,远程过程调用)项目中,单例模式是一种常见的设计模式,用于确保某个类只有一个实例,并提供一个全局访问点。单例模式在 RPC 项目中特别有用,例如用于管理配置、日志记录器或连接池等资源。

例如:在 RPC 项目中使用单例模式来管理一个全局的配置对象

复制代码
#include <iostream>
#include <mutex>
#include <memory>

class ConfigManager {
private:
    // 静态指针,存储唯一的实例
    static std::unique_ptr<ConfigManager> instance;

    // 私有化构造函数,防止外部直接构造
    ConfigManager() {
        std::cout << "ConfigManager initialized." << std::endl;
    }

    // 禁止拷贝构造和赋值操作
    ConfigManager(const ConfigManager&) = delete;
    ConfigManager& operator=(const ConfigManager&) = delete;

public:
    // 提供一个静态方法获取实例
    static ConfigManager& getInstance() {
        static std::once_flag onceFlag;
        std::call_once(onceFlag, []() {
            instance = std::make_unique<ConfigManager>();
        });
        return *instance;
    }

    // 示例方法:获取配置值
    std::string getConfigValue(const std::string& key) {
        // 这里可以实现从配置文件或数据库中读取配置
        return "value_for_" + key;
    }
};

// 初始化静态成员变量
std::unique_ptr<ConfigManager> ConfigManager::instance = nullptr;

使用单例

复制代码
#include <iostream>

int main() {
    // 获取单例实例
    ConfigManager& configManager = ConfigManager::getInstance();

    // 使用单例实例
    std::string value = configManager.getConfigValue("database_url");
    std::cout << "Config Value: " << value << std::endl;

    return 0;
}

代码理解:

  1. 私有化构造函数
    • ConfigManager():构造函数被私有化,防止外部直接构造对象。
    • 禁止拷贝构造和赋值操作,确保类的实例唯一性。
  2. 静态实例
    • static std::unique_ptr<ConfigManager> instance;:静态成员变量,存储唯一的实例。
    • 使用 std::unique_ptr 确保实例在程序结束时自动释放。
  3. 全局访问点
    • static ConfigManager& getInstance():提供一个静态方法,用于获取唯一的实例。
    • 使用 std::call_oncestd::once_flag 确保实例只被初始化一次,即使在多线程环境下也能安全工作。
  4. 延迟初始化
    • 实例仅在第一次调用 getInstance() 时初始化,避免不必要的资源占用。
相关推荐
我不想当小卡拉米几秒前
C++:继承+菱形虚拟继承的一箭双雕
开发语言·jvm·c++
机器视觉知识推荐、就业指导18 分钟前
QML 批量创建模块 【Repeater】 组件详解
前端·c++·qml
橙橙子23043 分钟前
c++柔性数组、友元、类模版
开发语言·c++·柔性数组
阳光_你好2 小时前
请详细说明opencv/c++对图片缩放
c++·opencv·计算机视觉
杰克逊的黑豹2 小时前
不再迷茫:Rust, Zig, Go 和 C
c++·rust·go
Y.O.U..2 小时前
今日八股——C++
开发语言·c++·面试
Zhichao_972 小时前
【UE5 C++课程系列笔记】33——商业化Json读写
c++·ue5
云边有个稻草人3 小时前
【C++】第八节—string类(上)——详解+代码示例
开发语言·c++·迭代器·string类·语法糖auto和范围for·string类的常用接口·operator[]
惊鸿一博3 小时前
c++ &&(通用引用)和&(左值引用)区别
开发语言·c++
高 朗4 小时前
2025高频面试设计模型总结篇
设计模式·面试·职场和发展