0
点赞
收藏
分享

微信扫一扫

部署一个在线OCR工具

朱小落 2024-02-14 阅读 39

6 种 单例 的手写,都是懒汉(饿汉代码在 “懒汉 / 饿汉的区别”)

目录

✊前言 

🌼GPT解析

🌼概念解析

RAII

懒汉 / 饿汉的区别

特点

举例

单例 -- 伪代码

适用场景

单例 -- 实现方式

优缺点

🎂手写 6 种单例模式

🐬(一)懒汉 -- 内存泄露

🐬(二)懒汉 -- 解决内存泄漏

🐆(三)懒汉 -- 双检锁

🐆(四)原子操作

🐘(五)C++11 -- magic static

🐘(六)模板类


✊前言 

TinyWebServer 涉及到单例模式,花个 5 小时学习一下

参考文章👇 + GPT

🌼GPT解析

概念

优势

劣势

应用

🌼概念解析

RAII

懒汉 / 饿汉的区别

  • 懒汉式 -- 时间换空间 -- 访问量较(推荐内部静态变量的懒汉单例,代码量少)
  • 饿汉式 -- 空间换时间 -- 访问量较OR 线程较多的的情况

懒汉模式

// Singleton类的定义
class Singleton {
public:
    // 获取单例对象的静态成员函数
    static Singleton* GetInstance() {
        // 如果instance为空,即还没有创建单例对象
        if (instance == nullptr) {
            // 创建一个新的Singleton对象
            instance = new Singleton();
        }
        // 返回单例对象的指针
        return instance;
    }

private:
    // 构造函数和析构函数,设置为默认实现
    Singleton() = default;
    ~Singleton() = default;
    // 禁用拷贝构造函数和赋值运算符重载,确保单例对象唯一性和不会被复制
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    // 静态成员变量,指向单例对象。初始值为nullptr,表示还未创建单例对象
    static Singleton *instance;
};

// 初始化静态成员变量
Singleton* Singleton::instance = nullptr;

懒汉 -- 解释

饿汉模式

class Singleton {
public:
    static Singleton* GetInstance() {
        return instance;
    }

private:
    Singleton() = default; // 默认构造函数,设为 private,防止外部实例化对象
    ~Singleton() = default; // 默认析构函数,设为 private,防止外部删除对象
    Singleton(const Singleton&) = delete; // 删除拷贝构造函数,防止对象被复制
    Singleton& operator=(const Singleton&) = delete; // 删除赋值运算符重载,防止对象被赋值

    static Singleton *instance; // 静态成员变量,指向单例对象

};

Singleton* Singleton::instance = new Singleton(); // 在程序启动时即创建单例对象

特点

举例

单例 -- 伪代码

/* 
数据库类会对`getInstance() 获取实例`方法
进行定义,以便客户端在程序各处都能访问
相同的数据库连接实例
*/
class Database is
    // 保存单例实例的成员变量 -- 声明为静态
    private static field instance : Database

    // 单例的构造函数 -- 私有,防止`new`运算符
    // 直接调用构造方法
    private constructor Database() is
        // 初始化代码(eg:数据库服务器的实际连接)

    // 用于控制对单例实例的访问权限的静态方法
    public static method getInstance() is
        if (Database.instance == null) 
            acquireThreadLock() 
                // 确保在该线程等待解锁时
                // 其他线程没有初始化该实例
                if (Database.instance == null) 
                    Database.instance = new Database()
        return Database.instance

    // 最后,任何单例都必须定义一些可在其实例上执行的业务逻辑
    public method query(sql) is
        // eg: 所有数据库查询请求都需通过该方法进行
        // 因此,可以在这里添加限流或缓冲逻辑

class Application is
    method main() is
        Database foo = Database.getInstance()
        foo.query("SELECT ...")

        Database bar = Database.getInstance()
        bar.query("SELECT ...")
        // 变量 `bar` 和 `foo` 中包含同一个对象

适用场景

🧜‍1)某个类,对于所有客户端,只有一个可用实例

🤴2)需要严格控制全局变量

单例 -- 实现方式

优缺点

单例拥有与全局变量相同的优缺点。 尽管它们非常有用, 但却会破坏代码的模块化特性 

优点 

缺点

🎂手写 6 种单例模式

大厂面试真题

🐬(一)懒汉 -- 内存泄露

// 版本一:简单懒汉模式,存在内存泄漏和线程安全问题

class Singleton1 {
public:
    // 获取单例实例的静态方法
    // 用于获取 Singleton1 类的唯一实例
    static Singleton1* GetInstance() { 
        if (instance == nullptr)
            instance = new Singleton1(); // 创建新实例
        return instance; // 返回 Singleton1 实例
    }

private:
    // 防止外界 构造/删除 对象
    Singleton1() = default; // 私有构造
    ~Singleton1() = default; // 私有析构函数
    Singleton1(const Singleton1&) = default; // 禁止拷贝构造
    // 禁止赋值
    Singleton1& operator = (const Singleton1&) = default;

    // 静态成员指针,指向唯一实例
    static Singleton1 *instance; 
};

Singleton1* Singleton1::instance = nullptr;
/* 
存在内存泄漏问题,instance 在堆上,需要手动释放
但是外界无法调用delete释放对象(私有静态成员指针)
&& 
线程安全问题 -- 多个线程同时分配内存
*/

🐬(二)懒汉 -- 解决内存泄漏

补充知识👇

代码 

// 版本二:解决内存泄漏,但仍存在线程安全问题
class Singleton2 {
public:
    // 获取单例实例的静态方法
    static Singleton2* GetInstance() {
        if (instance == nullptr) {
            instance = new Singleton2();
            atexit(Destructor); // 程序退出时释放
        }
        return instance;
    }

private:
    // 防止外界 构造/删除 对象
    Singleton2() = default; // 私有构造
    ~Singleton2() = default; // 私有析构
    Singleton2(const Singleton2&) = default; // 禁止拷贝构造
    // 禁止赋值 -- 通过定义为私有,避免外界调用,达到了禁止的目的
    Singleton2& operator=(const Singleton2&) = default;

    // 释放实例资源的静态方法
    static void Destructor() {
        if (instance != nullptr) {
            delete instance;
            instance = nullptr;
        }
    }

    // 静态成员指针,指向唯一实例
    static Singleton2 *instance; 
};

Singleton2* Singleton2::instance = nullptr;
/*
解决了内存泄漏,程序退出时释放对象
但仍存在多个线程同时分配内存的问题
*/

🐆(三)懒汉 -- 双检锁

需要注意的是,双检锁 -- Java下正确,Cpp下错误。具体参考👇

Ubuntu Pastebin

补充解释

关于 双检锁 两个 if 的解释

双检锁 -- 最终效果:只有第一个线程加锁,并创建单例实例

代码

/*
版本三
双重检查锁(double-checked-locking)实现线程安全
*/

class Singleton3 {
public:
    // 获取单例实例的静态方法
    // 静态成员函数 返回 指向 Singleton3 类型对象的指针
    static Singleton3* GetInstance() {
        // 双检锁 - 两个 if,只有指针为空才加锁,避免每次调用
        // GetInstance() 都加锁,降低加锁的开销
        if (instance == nullptr) {
            lock_guard<mutex> lock(mutex_); // 第一个线程加锁
            if (instance == nullptr) {
                instance = new Singleton3(); // 第一个线程创建实例
                atexit(Destructor);
            }
        }
        return instance;
    }

private:
    // 防止外界 构造/删除 对象
    Singleton3() = default; // 私有构造
    ~Singleton3() = default; // 私有析构
    Singleton3(const Singleton3&) = default; // 禁止拷贝
    Singleton3& operator=(const Singleton3&) = default; // 禁止赋值

    // 释放实例资源的静态方法
    static void Destructor() {
        if (instance != nullptr) {
            delete instance;
            instance = nullptr;
        }
    }

    static Singleton3 *instance; // 静态成员指针,指向唯一实例
    static mutex mutex_; // 互斥锁,用于线程安全
}

// 定义静态指针成员 instance,用于保存 Singleton3 类的唯一实例
Singleton3* Singleton3::instance = nullptr;
// nullptr 表示未创建实例

🐆(四)原子操作

补充解释

代码 

// 版本四:原子操作 + 内存屏障 保证线程安全

class Singleton4 {
public:
    // 获取实例的静态方法
    // 静态成员函数 返回 指向 Singleton4 类型对象的指针
    static Singleton4* GetInstance() {
        // 加载单例实例指针
        Singleton4* tmp = instance.load(std::memory_order_relaxed);
        // 获取内存屏障 - 确保前面操作完成
        atomic_thread_fence(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 Singleton4; // 创建实例
                // 释放内存屏障 - 确保存储操作不被重排序
                atomic_thread_fence(std::memory_order_release);
                // 存储新创建的实例指针
                instance.store(tmp, std::memory_order_relaxed);
                atexit(Destructor); // 注册析构函数
            }
        }
        return tmp;
    }

private:
    // 防止外界 构造/删除 对象
    Singleton4() = default; // 私有构造
    ~Singleton4() = default; // 私有析构
    Singleton4(const Singleton4&) = default; // 拷贝构造
    Singleton4& operator=(const Singleton4&) = default; // 赋值

    // 释放实例资源的静态方法
    static void Destructor() {
        if (instance != nullptr) {
            delete instance; // 删除实例
            instance = nullptr; // 重置指针
        }
    }

    static std::atomic<Singleton4*> instance; // 原子指针 - 线程安全
    static std::mutex mutex_; // 互斥锁 - 用于双检锁
};

std::atomic<Singleton4*> Singleton4::instance; 
std::mutex Singleton4::mutex_;

🐘(五)C++11 -- magic static

补充解释

关于 new 操作带来的 cpu reorder 操作 

简而言之 -- new 和 多线程 可能会产生冲突(需要采取其他措施来规避 BUG)

代码

有了 C++11 magic static 后,就不用在 懒汉模式 下加锁了,因为静态成员只初始化 1 次

// 版本五:C++11 magic static 特性实现线程安全

class Singleton5 {
public:
    // 获取单例实例
    static Singleton5& GetInstance() {
        /*
        (1)静态局部变量初始化时,并发线程同时进入声明语句,
        并发线程会阻塞等待其初始化结束,保证线程安全
        (2)静态局部变量首次经过它的声明才会被初始化,在其后所有调用中,
        声明都会被跳过
        (3)So, 定义在栈上的局部静态变量保存单例对象的
        优势:延迟加载;系统自动调用析构函数;回收内存;
        没有 new 操作 带来的 cpu reorder 操作;线程安全
        */
        static Singleton5 instance; // magic static 特性
        return instance;
    }
private:
    // 防止外界 构造/删除 对象
    Singleton5() = default; // 私有构造
    ~Singleton5() = default; // 私有析构
    Singleton5(const Singleton5&) = default; // 拷贝构造
    Singleton5& operator=(const Singleton5&) = default; // 赋值
};

🐘(六)模板类

补充解释

 模板复习

代码

// 版本六:模板实现单例模式

template<typename T>
class Singleton6 {
public:
    // 获取单例实例
    static T& GetInstance() {
        static T instance;
        return instance;
    }

protected: // 为了基类可以 构造/析构(仅限类内部和基类使用)
    // 析构虚函数, 支持多态,确保子类析构
    virtual ~Singleton6() = default; 
    Singleton6() = default; // 私有构造
    Singleton6(const Singleton6&) = default; // 禁止拷贝构造
    Singleton6& operator=(const Singlton6&) = default; // 禁止赋值
};

// Singleton6<DesignPattern> 指定模板类型为 DesingnPattern
class DesignPattern : public Singleton6<DesignPattern> {
    // 友元:基类访问父类私有不受限制
    friend class Singleton6<DesignPattern>;

public:
    ~DesignPattern() = default; // 析构在public, 便于手动删除对象

private:
    DesignPattern() = default; // 私有构造
    DesignPattern(const DesignPattern&) = default; // 禁止拷贝
    // 禁止赋值
    DesignPattern& operator=(const Designpattern&) = default;
};

int main()
{
    // 获取单例实例
    DesignPattern &d1 = DesignPattern::Getinstance();
}
举报

相关推荐

0 条评论