设计模式之单例模式

本文详细介绍了单例模式的概念、用途,以及如何在C++中实现单例模式,包括懒汉模式和饿汉模式。懒汉模式在多线程环境下可能存在线程安全问题,而饿汉模式虽然没有此问题但可能导致资源浪费。C++11的懒汉模式解决了线程安全问题。此外,文章还讨论了单例模式的优缺点和适用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

文章目录

目录

一、单例模式是什么?

1.定义:

2.为什么要用单例模式:

二、如何实现单例

1.基本创建

三.懒汉和饿汉模式

1.懒汉模式:

1.1线程安全问题

1.2 c++11懒汉模式

2.饿汉模式

3.两种模式的对比:

四.个人理解

总结

单例模式优点:

单例模式缺点:

适用场景:


一、单例模式是什么?

1.定义:

        单例模式是一种设计模式,它保证了一个类只有一个对象,具体分为懒汉模式以及饿汉模式。

        单例模式有三个要点:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例

2.为什么要用单例模式:

某些类只需要一个对象,多个对象的创建浪费资源,比如某些管理器。

 对于某些类,我们只需要一个对象实例,当一个实例创建成功,所有操作都基于这个实例来完成。这时就会用到单例模式。

二、如何实现单例

为了防止外部创建对象,我们将构造函数私有化,并在内部提供静态私有指针变量,设置一个公共的静态方法返回对象实例。

1.基本创建

class animal
{
private: 
    animal() {}
         static animal* a ;
         
public:
    static animal* get()
    {
        if (a == nullptr)
        {
           
            a = new animal;
        }
        return a;
    }
};
animal* animal::a = nullptr;

设置成静态可在类外访问,无需创建对象,获取对象直接通过get获取唯一对象实例(分配空间)。静态变量,在类外初始化。

三.懒汉和饿汉模式

1.懒汉模式:

懒汉模式会在需要的时候创建并返回类的实例,和上面基本创建类似,但是懒汉模式会存在线程安全问题,需要加锁。

class animal
{
private: 
    animal() {}
         static animal* a ;
         static  mutex* mut;
public:
    static animal* get()
    {
        mut->lock();
        if (a == nullptr)
        {
            a = new animal;
        }
        mut->unlock();
        return a ;
    }
};
animal* animal::a = nullptr;
mutex* animal::mut = new mutex();

1.1线程安全问题

class animal
{
private: 
    animal() {}
         static animal* a ;
         static mutex mt;
public:
    static animal* get()
    {
        if (a == nullptr)
        {
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
            a = new animal;
        }
        return a;
    }
};
animal* animal::a = nullptr;

void fun()
{
    animal* q = animal::get();
    
    cout << q;
}
int main()
{
    
    for (int i = 0; i < 2; i++)
    {
        thread* th = new thread(fun);
    }
        
    
    while (1);
}

这里说一下volatile,这个关键字会声明一个可能在程序控制之外被修改的变量,也就说可能是多线程访问的变量,有些编译器可能会将多次被访问的变量存入寄存器,避免了多次的多次的读入,但是存的时候可能已被修改,但是寄存器中还是未修改,这时使用volatile声明保证了变量多线程的可见性,也就是禁止存入寄存器,都从内存中操作,但这只保证了可见性,实际访问还得用锁,保证原子性。

当我们在多线程下使用单例模式创建实例,会出现创建出多个实例的情况:

 可以看到,得到的两个地址是不同的。

原因:当第一个线程想要获取并创建实例之前,另一个线程也在获取,这时实例没有创建完成,导致两个线程都认为自己是先到的,然后创建了两个实例。

解决办法:加锁,同一时刻只能有一个线程去创建。

1.2 c++11懒汉模式

c++11 特性,如果多个线程尝试初始化静态局部变量,那么只会初始化一次。

2.饿汉模式

饿汉模式,编译时创建唯一实例(分配空间),没有线程安全问题,但是浪费资源。

class animal
{
private: 
    animal() {}
         static animal* a ;
        
public:
    static animal* get()
    {  
            return a ;       
    }
};
animal* animal::a = new animal();

3.两种模式的对比:

从速度上看:饿汉模式提前创建好实例,相较于懒汉模式,速度更快。

从空间利用看:懒汉模式在使用时才实例化,空间利用率更高。

四.个人理解

1.为什么不直接 static animal a; 然后返回&a?

这个a属于静态变量声明,需要类外初始化,如果是指针*a 可以初始化为nullptr,a没法初始化,会有错误。而c++11懒汉模式,中的a是局部静态对象,如果发现多线程初始化则会阻塞相应线程,保证单例。

是否会造成线程安全问题,在于是否会进行多次实例化,而静态局部变量只会初始化一次,不用实例化。


总结

单例模式优点:

1.系统内存中只存在一个对象,因此可以节约系统资源。

2.因为单例类封装了它的唯一实例,所以它可以严格控制客户怎样以及何时访问它。

单例模式缺点:

1.由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。

2. 单例类的职责过重,在一定程度上违背了“单一职责原则”。

3.现在很多面向对象语言(如Java、C#)的运行环境都提供了自动垃圾回收的技术,因此,如果实例化的共享对象长时间不被利用,系统会认为它是垃圾,会自动销毁并回收资源,下次利用时又将重新实例化,这将导致共享的单例对象状态的丢失。

适用场景:

 (1) 系统只需要一个实例对象,如系统要求提供一个唯一的序列号生成器或资源管理器,或者需要考虑资源消耗太大而只允许创建一个对象。

 (2) 客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BlackMonkeyHH

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值