飞道的博客

C++单例模式的实现(懒汉式、饿汉式)

399人阅读  评论(0)

名词解释

数学与逻辑学中,singleton定义为:有且仅有一个元素的集合
单例模式最初的定义出现在《设计模式》(艾迪生维斯理,1994):"保证一个类仅有一个实例,并提供一个访问它的全局访问点。"

动机

对于系统中的某些类来说,只有一个实例很重要:

例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或ID(序号)生成器。

如在Windows中就只能打开一个任务管理器。如果不使用机制对窗口对象进行唯一化,将弹出多个窗口,如果这些窗口显示的内容完全一致,则是重复对象,浪费内存资源;如果这些窗口显示的内容不一致,则意味着在某一瞬间系统有多个状态,与实际不符,也会给用户带来误解,不知道哪一个才是真实的状态。
因此有时确保系统中某个对象的唯一性一个类只能有一个实例非常重要。

要点

根据定义,显然单例模式的要点有三个:

  1. 某个类只能有一个实例;
  2. 它必须自行创建这个实例;
  3. 它必须自行向整个系统提供这个实例。

从具体实现角度来说:

  1. 单例模式的类只提供私有的构造函数
  2. 类定义中含有一个该类的静态私有对象
  3. 该类提供了一个静态的公有函数用于创建或者获取它本身的静态私有对象。

饿汉式

根据名字就可以知道:饿汉
因为饿,所以猴急,巴不得第一时间就把食物送到嘴边;
因此,这个模式的单例模式唯一的实例是在主函数运行之前就产生了的!

实现方式

根据上述要点,不难实现如下代码:

class Singleton
{
   
public:
    static Singleton *getinstance()
    {
   
        return &instance;
    }
private:
    static Singleton instance;
    Singleton() {
   }
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
};
Singleton Singleton::instance;

运行结果

通过如下测试代码,我们来看饿汉单例模式的结果:

int main()
{
   
    Singleton *a1 = Singleton::getinstance();
    Singleton *a2 = Singleton::getinstance();
    Singleton *a3 = Singleton::getinstance();

    cout << a1 << endl;
    cout << a2 << endl;
    cout << a3 << endl;

    return 0;
}

根据定义我们知道,a1a2a3打印出来应该是同一个地址

可以看到,是同一个地址

懒汉式

有了上面饿汉式的经验,我们同样可以这么想:
懒汉式,因为,所以唯一实例能拖就拖
一直等到有人要使用的时候,才不得不构造

常规实现

同样的,根据定义我们可以写出如下代码:

class Singleton
{
   
public:
    static Singleton *getinstance()
    {
   
        if (instance == nullptr)
            instance = new Singleton();
        return instance;
    }

private:
    static Singleton *instance;
    Singleton() {
   }
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
};
Singleton *Singleton::instance = nullptr;

运行结果:

虽然运行结果没有问题,
但是,这个代码是有问题的:
线程不安全

对于if (instance == nullptr)instance = new Singleton()这两句来说,如果处在多线程环境下:

我们会发现这种情况下,会执行多次对象的构造,这和我们的初衷不符!

线程安全版实现

为了避免上述问题,我们改进代码:
加入线程互斥锁(mutex)

std::mutex mtx;
class Singleton
{
   
public:
    static Singleton* getinstance()
    {
   
        if(instance == nullptr)
        {
   
            lock_guard<std::mutex> lck(mtx);
            if(instance == nullptr)
            {
   
                instance = new Singleton();
            }
        }
        return instance;
    }
private:
    static Singleton* instance;
    Singleton(){
   }
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
};
Singleton* Singleton::instance = nullptr;

注意:
为了避免线程安全问题:需要进行锁+两次判断
避免因为一个线程后续操作未完成,而使得后来的线程进入if语句

可以看到运行正确

精简实现

对于懒汉式来说,还有更精简的一种写法:

class Singleton
{
   
public:
    static Singleton* getinstance()
    {
   
        static Singleton instance;
        return &instance;
    }
private:
    Singleton(){
   }
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
};

运行结果如下:

注意:
这个精简版之所以也是线程安全的,是因为,在底层对于static静态局部变量的初始化,编译器会自动加锁和解锁
将上述代码在Linux通过g++编译,命令如下:
g++ -o 单例模式 单例模式.cpp -g

接下来启动gdb调试:
gdb 单例模式 -q

接下来输入l:(注意是英文L的小写

接着下断点:b 10

接着运行到断点处:run

接下来执行:disassemble getinstance

我们可以看到,底层的汇编是自动加锁解锁的!

参考资料

【1】单例模式(百度百科)


转载:https://blog.csdn.net/m0_46308273/article/details/117088976
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场