单例模式概念

今天给大家讲讲单例模式演变流程,从C++98到C++11经历了哪些变化?哪一种单例模式更为安全。

单例模式(Singleton Pattern)是一种创建型设计模式,旨在确保一个类在整个应用程序生命周期中仅且只有一个实例,并提供一个全局访问点以获取该实例。设计单例模式的主要原因和作用包括以下几个方面:

1. 控制实例数量

单例模式确保一个类只有一个实例,防止在程序中创建多个实例可能导致的资源浪费或状态不一致问题。例如,数据库连接池、配置管理类等通常使用单例模式,以确保全局只有一个连接池或配置实例。

2. 提供全局访问点

单例模式通过提供一个全局访问点,使得在程序的任何地方都可以方便地访问该实例。这对于那些需要在多个模块或组件之间共享的资源或服务尤为重要,如日志记录器、缓存管理器等。

3. 延迟实例化

单例模式通常采用懒加载的方式,即在第一次需要使用实例时才创建。这有助于节省系统资源,特别是在实例创建成本较高或初期并不需要该实例的情况下。

4. 避免命名冲突

通过将单例实例作为一个类的静态成员,可以避免在全局命名空间中引入多个实例,减少命名冲突的风险。

5. 管理共享资源

在多线程环境下,单例模式可以有效管理共享资源,确保线程安全。例如,操作系统中的线程池、任务管理器等常使用单例模式,以协调多个线程对资源的访问。

设计单例模式的考虑因素

虽然单例模式有诸多优点,但在设计和使用时也需要注意以下几点:

  • 线程安全:在多线程环境下,需要确保单例实例的创建和访问是线程安全的,常用的方法有双重检查锁定(Double-Checked Locking)和使用静态内部类等。
  • 延迟初始化:根据需求选择是否采用延迟初始化,以平衡性能和资源利用。
  • 可测试性:单例模式可能会影响代码的可测试性,特别是在单元测试中,可能需要通过依赖注入等手段来替代单例实例。
  • 限制扩展:单例模式通过限制实例数量可能会限制类的扩展性,需谨慎使用。

适用场景

  • 需要确保全局只有一个实例的场景,如配置管理、日志系统、设备驱动等。
  • 需要全局访问点来协调系统中的多个部分,如缓存、线程池等。

不适用场景

  • 需要多个实例以满足不同需求的场景。
  • 对象的生命周期需要更灵活控制的场合。

总的来说,单例模式通过控制类的实例数量和提供全局访问点,为系统资源管理和状态一致性提供了有效的解决方案。然而,在实际应用中,应根据具体需求和上下文环境,谨慎决定是否使用单例模式,以避免潜在的设计问题。

局部静态变量方式

这种方式最简单

//通过静态成员变量实现单例
//懒汉式
class Single2
{
private:
    Single2()
    {
    }
    Single2(const Single2 &) = delete;
    Single2 &operator=(const Single2 &) = delete;
public:
    static Single2 &GetInst()
    {
        static Single2 single;
        return single;
    }
};

上述代码通过局部静态成员single实现单例类,原理就是函数的局部静态变量生命周期随着进程结束而结束。上述代码通过懒汉式的方式实现。 调用如下

void test_single2()
{
    //多线程情况下可能存在问题
    cout << "s1 addr is " << &Single2::GetInst() << endl;
    cout << "s2 addr is " << &Single2::GetInst() << endl;
}

程序输出如下

sp1  is  0x1304b10
sp2  is  0x1304b10

确实生成了唯一实例,在C++98年代,上述单例模式存在隐患,对于多线程方式生成的实例可能时多个。

随着C++ 11的来临,这种方式不再存在线程安全问题,是最为简单也是最适合新手的方式。

静态成员变量指针方式(饿汉式)

可以定义一个类的静态成员变量,用来控制实现单例,这种方式依靠静态成员提前初始化保证生成的单例是唯一的。

//饿汉式
class Single2Hungry
{
private:
    Single2Hungry()
    {
    }
    Single2Hungry(const Single2Hungry &) = delete;
    Single2Hungry &operator=(const Single2Hungry &) = delete;
public:
    static Single2Hungry *GetInst()
    {
        if (single == nullptr)
        {
            single = new Single2Hungry();
        }
        return single;
    }
private:
    static Single2Hungry *single;
};

这么做的一个好处是我们可以通过饿汉式的方式避免线程安全问题

//饿汉式初始化
Single2Hungry *Single2Hungry::single = Single2Hungry::GetInst();
void thread_func_s2(int i)
{
    cout << "this is thread " << i << endl;
    cout << "inst is " << Single2Hungry::GetInst() << endl;
}
void test_single2hungry()
{
    cout << "s1 addr is " << Single2Hungry::GetInst() << endl;
    cout << "s2 addr is " << Single2Hungry::GetInst() << endl;
    for (int i = 0; i < 3; i++)
    {
        thread tid(thread_func_s2, i);
        tid.join();
    }
}
int main(){
    test_single2hungry()
}

程序输出如下

s1 addr is 0x1e4b00
s2 addr is 0x1e4b00
this is thread 0
inst is 0x1e4b00
this is thread 1
inst is 0x1e4b00
this is thread 2
inst is 0x1e4b00

可见无论单线程还是多线程模式下,通过静态成员变量的指针实现的单例类都是唯一的。

饿汉式是在程序启动时就进行单例的初始化,这种方式也可以通过懒汉式调用,无论饿汉式还是懒汉式都存在一个问题,就是什么时候释放内存?

多线程情况下,释放内存就很难了,还有二次释放内存的风险。

静态成员变量指针方式(懒汉式)

我们定义一个单例类并用懒汉式方式调用

//懒汉式指针
//即使创建指针类型也存在问题
class SinglePointer
{
private:
    SinglePointer()
    {
    }
    SinglePointer(const SinglePointer &) = delete;
    SinglePointer &operator=(const SinglePointer &) = delete;
public:
    static SinglePointer *GetInst()
    {
        if (single != nullptr)
        {
            return single;
        }
        s_mutex.lock();
        if (single != nullptr)
        {
            s_mutex.unlock();
            return single;
        }
        single = new SinglePointer();
        s_mutex.unlock();
        return single;
    }
private:
    static SinglePointer *single;
    static mutex s_mutex;
};

cpp文件里初始化静态成员,并定义一个测试函数

//懒汉式
//在类的cpp文件定义static变量
SinglePointer *SinglePointer::single = nullptr;
std::mutex SinglePointer::s_mutex;
void thread_func_lazy(int i)
{
    cout << "this is lazy thread " << i << endl;
    cout << "inst is " << SinglePointer::GetInst() << endl;
}
void test_singlelazy()
{
    for (int i = 0; i < 3; i++)
    {
        thread tid(thread_func_lazy, i);
        tid.join();
    }
    //何时释放new的对象?造成内存泄漏
}
int main(){
    test_singlelazy();
}

函数输出如下

this is lazy thread 0
inst is 0xbc1700
this is lazy thread 1
inst is 0xbc1700
this is lazy thread 2
inst is 0xbc1700

此时生成的单例对象的内存空间还没回收,这是个问题,另外如果多线程情况下多次delete也会造成崩溃。

C++11改进

我们可以利用C++11 提供的once_flag实现安全的创建

#ifndef DAY34_SINGLETON_SINGLETON_H
#define DAY34_SINGLETON_SINGLETON_H

#include <mutex>
#include <iostream>

class SingletonOnceFlag{
public:
    static SingletonOnceFlag* getInstance(){
        static std::once_flag flag;
        std::call_once(flag, []{
            _instance = new SingletonOnceFlag();
        });
        return _instance;
    }

    void PrintAddress() {
        std::cout << _instance << std::endl;
    }
    ~SingletonOnceFlag() {
        std::cout << "this is singleton destruct" << std::endl;
    }
private:
    SingletonOnceFlag() = default;
    SingletonOnceFlag(const SingletonOnceFlag&) = delete;
    SingletonOnceFlag& operator=(const SingletonOnceFlag& st) = delete;
    static SingletonOnceFlag* _instance;

};


#endif //DAY34_SINGLETON_SINGLETON_H

static成员要在cpp中初始化

SingletonOnceFlag *SingletonOnceFlag::_instance = nullptr;

测试

#include "Singleton.h"
#include <thread>
#include <mutex>
int main() {
    system("chcp 65001 > nul");
    std::mutex mtx;
    std::thread t1([&](){
        SingletonOnceFlag::getInstance();
        std::lock_guard<std::mutex> lock(mtx);
        SingletonOnceFlag::getInstance()->PrintAddress();
    });

    std::thread t2([&](){
        SingletonOnceFlag::getInstance();
        std::lock_guard<std::mutex> lock(mtx);
        SingletonOnceFlag::getInstance()->PrintAddress();
    });

    t1.join();
    t2.join();

    return 0;
}

测试结果

0x19a74de7420
0x19a74de7420

智能指针方式(懒汉式)

可以利用智能指针自动回收内存的机制设计单例类

#ifndef DAY34_SINGLETON_SINGLETON_H
#define DAY34_SINGLETON_SINGLETON_H

#include <mutex>
#include <iostream>
#include <memory>

class SingletonOnceFlag{
public:
    static std::shared_ptr<SingletonOnceFlag> getInstance(){
        static std::once_flag flag;
        std::call_once(flag, []{
            _instance = std::shared_ptr<SingletonOnceFlag>(new SingletonOnceFlag());
        });
        return _instance;
    }

    void PrintAddress() {
        std::cout << _instance << std::endl;
    }
    ~SingletonOnceFlag() {
        std::cout << "this is singleton destruct" << std::endl;
    }
private:
    SingletonOnceFlag() = default;
    SingletonOnceFlag(const SingletonOnceFlag&) = delete;
    SingletonOnceFlag& operator=(const SingletonOnceFlag& st) = delete;
    static std::shared_ptr<SingletonOnceFlag> _instance;

};


#endif //DAY34_SINGLETON_SINGLETON_H

同样在SingletonOnceFlag.cpp中进行单例成员的初始化

#include "Singleton.h"

std::shared_ptr<SingletonOnceFlag> SingletonOnceFlag::_instance = nullptr;

再次测试

#include <iostream>
#include "Singleton.h"
#include <thread>
#include <mutex>
int main() {
    system("chcp 65001 > nul");
    std::mutex mtx;
    std::thread t1([&](){
        SingletonOnceFlag::getInstance();
        std::lock_guard<std::mutex> lock(mtx);
        SingletonOnceFlag::getInstance()->PrintAddress();
    });

    std::thread t2([&](){
        SingletonOnceFlag::getInstance();
        std::lock_guard<std::mutex> lock(mtx);
        SingletonOnceFlag::getInstance()->PrintAddress();
    });

    t1.join();
    t2.join();

    return 0;
}

这次输出析构信息

0x1d620a47420
0x1d620a47420
this is singleton destruct

辅助类智能指针单例模式

智能指针在构造的时候可以指定删除器,所以可以传递一个辅助类或者辅助函数帮助智能指针回收内存时调用我们指定的析构函数。

//
// Created by secon on 2025/3/1.
//

#ifndef DAY34_SINGLETON_SINGLETON_H
#define DAY34_SINGLETON_SINGLETON_H

#include <mutex>
#include <iostream>
#include <memory>

class SingleAutoSafe;
class SafeDeletor
{
public:
    void operator()(SingleAutoSafe *sf)
    {
        std::cout << "this is safe deleter operator()" << std::endl;
        delete sf;
    }
};

class SingleAutoSafe{
public:
    static std::shared_ptr<SingleAutoSafe> getInstance(){
        static std::once_flag flag;
        std::call_once(flag, []{
            _instance = std::shared_ptr<SingleAutoSafe>(new SingleAutoSafe(), SafeDeletor());
        });
        return _instance;
    }

    void PrintAddress() {
        std::cout << _instance << std::endl;
    }
    //定义友元类,通过友元类调用该类析构函数
    friend class SafeDeletor;
private:
    SingleAutoSafe() = default;
    SingleAutoSafe(const SingleAutoSafe&) = delete;
    SingleAutoSafe& operator=(const SingleAutoSafe& st) = delete;
    ~SingleAutoSafe() {
        std::cout << "this is singleton destruct" << std::endl;
    }
    static std::shared_ptr<SingleAutoSafe> _instance;

};


#endif //DAY34_SINGLETON_SINGLETON_H

在cpp文件中实现静态成员的定义


#include "Singleton.h"

std::shared_ptr<SingleAutoSafe> SingleAutoSafe::_instance = nullptr;

SafeDeletor要写在SingleAutoSafe上边,并且SafeDeletor要声明为SingleAutoSafe类的友元类,这样就可以访问SingleAutoSafe的析构函数了。

我们在构造single时制定了SafeDeletor(),single在回收时,会调用SingleAutoSafe的仿函数,从而完成内存的销毁。

并且SingleAutoSafe的析构函数为私有的无法被外界手动调用了。

测试

#include <iostream>
#include "Singleton.h"
#include <thread>
#include <mutex>
int main() {
    system("chcp 65001 > nul");
    std::mutex mtx;
    std::thread t1([&](){
        SingleAutoSafe::getInstance();
        std::lock_guard<std::mutex> lock(mtx);
        SingleAutoSafe::getInstance()->PrintAddress();
    });

    std::thread t2([&](){
        SingleAutoSafe::getInstance();
        std::lock_guard<std::mutex> lock(mtx);
        SingleAutoSafe::getInstance()->PrintAddress();
    });

    t1.join();
    t2.join();

    return 0;
}

程序输出

0x1b379b07420
0x1b379b07420
this is safe deleter operator()

通用的单例模板类(CRTP)

我们可以通过声明单例的模板类,然后继承这个单例模板类的所有类就是单例类了。达到泛型编程提高效率的目的。

CRTP的概念

CRTP是一种将派生类作为模板参数传递给基类的技术,即一个类继承自一个以自身为模板参数的基类。这种模式常用于实现静态多态、接口的默认实现、编译时策略选择等。

比如

template <typename T>
class TempClass {
  //...  
};
//CRTP
class RealClass: public TempClass<RealClass>{
  //...  
};

单例基类实现

#include <memory>
#include <mutex>
#include <iostream>
using namespace std;
template <typename T>
class Singleton {
protected:
    Singleton() = default;
    Singleton(const Singleton<T>&) = delete;
    Singleton& operator=(const Singleton<T>& st) = delete;

    static std::shared_ptr<T> _instance;
public:
    static std::shared_ptr<T> GetInstance() {
        static std::once_flag s_flag;
        std::call_once(s_flag, [&]() {
            _instance = shared_ptr<T>(new T);
        });

        return _instance;
    }
    void PrintAddress() {
        std::cout << _instance.get() << endl;
    }
    ~Singleton() {
        std::cout << "this is singleton destruct" << std::endl;
    }
};

template <typename T>
std::shared_ptr<T> Singleton<T>::_instance = nullptr;

我们定义一个网络的单例类,继承上述模板类即可,并将构造和析构设置为私有,同时设置友元保证自己的析构和构造可以被友元类调用.

//通过继承方式实现网络模块单例
class SingleNet : public Singleton<SingleNet>
{
    friend class Singleton<SingleNet>;
private:
    SingleNet() = default;
public:
    ~SingleNet() {
        std::cout << "SingleNet destruct " << std::endl;
    }
};

测试案例

#include <iostream>
#include "Singleton.h"
#include <thread>
#include <mutex>
int main() {
    system("chcp 65001 > nul");
    std::mutex mtx;
    std::thread t1([&](){
        SingleNet::GetInstance();
        std::lock_guard<std::mutex> lock(mtx);
        SingleNet::GetInstance()->PrintAddress();
    });

    std::thread t2([&](){
        SingleNet::GetInstance();
        std::lock_guard<std::mutex> lock(mtx);
        SingleNet::GetInstance()->PrintAddress();
    });

    t1.join();
    t2.join();

    return 0;
}

程序输出

0x212248b7420
0x212248b7420
SingleNet destruct
this is singleton destruct

源码和视频

视频地址

源码地址

results matching ""

    No results matching ""