C++设计模式之单例模式

一 前言

  单例模式是面向对象设计模式中的一种,什么是单例模式,单例模式就是指某个类只可以实例化出一个对象,不可以实例化出多个对象,在工程中单例模式有比较多的应用,例如http服务器类,对于某网络服务来说http服务器一般只有一个,所以http服务器对象一般只有一个,再比如创建和销毁比较平凡的工具类,为了减少频繁的创建和销毁对象造成计算机效率低下,也通常使用单例模式;还有线程池、打印机这些只需要一个对象的类。
  知道了单例模式的定义,那么我们来思考如何设计单例模式呢?
  1. 单例模式只可以创建一个对象,那么构造函数必须是private类型的,否则外界将可以直接多次调用构造函数实例化多个对象,此外复制构造化函数没有存在的意义,只有一个对象,何来复制一说!
  2. 第二,我们需要一个标志符来表示当前类是否实例化出了对象,若已经实例化对象则不可以再实例化对象,否则可以实例化。
  3. 第三,析构函数也是私有的,一般单例模式实例化出的对象不会消亡,若消亡也需要和构造函数一样,在public中用相应的函数实现。
  4. 似乎目前只要考虑这些就行,我们先实现代码,看着这样行不行,若存在问题,再进行补充。

二 代码实现及改进

1. 初版代码

class Singleton {
public :
    static Singleton* getInstance() {
        if (instance == nullptr) {
            instance = new Singleton();
        }
        return instance;
    }
    
private :
    static Singleton* instance;
    Singleton() {};
    Singleton(Singleton &obj) = delete;
    ~Singleton() {}
};

Singleton *Singleton::instance = nullptr;

int main() {
    Singleton *p1 = Singleton::getInstance();
    Singleton *p2 = Singleton::getInstance();
    cout << "p1: " << p1 << ", p2: " << p2 << endl;
    return 0;
}

这里插一句:类属性在类中只是声明,需要程序员在类外进行定义。
代码运行结果如下:

在这里插入图片描述

2. 多线程带来的影响

  上面的代码从输出来看,似乎是没有问题的,但是却忽略了一点,那就是程序在多线程环境中运行存在错误,假设现在有A, B两个线程同时执行到第4行 if (instance == nullptr)判断,两个线程if判断均为真,都会实例化对象,这显然不行。那如何解决这个问题呢?答案就是:互斥锁(锁和信号量在多线程编程中扮演着重要的角色,需要牢记)。
  所以我们在类中加上互斥锁,防止此类问题的发生,代码如下:

class Singleton {
public :
    static Singleton* getInstance() {
        std::unique_lock<std::mutex> lock(m_mutex);
        if (instance == nullptr) {
            instance = new Singleton();
        }
        return instance;
    }
    
private :
    static std::mutex m_mutex;
    static Singleton* instance;
    Singleton() {};
    Singleton(Singleton &obj) = delete;
    ~Singleton() {}
};

Singleton *Singleton::instance = nullptr;
std::mutex Singleton::m_mutex;

  我们在函数开始加上锁,这样多线程环境中只有拿到互斥锁的线程才可以继续向下执行。有些读者可能会说这里只有加锁,没有解锁,其实对于unique_lock<>() 而言,出了其作用范围之后,就会自动解锁,故无需担心。
  但是这样就很好了吗?非也!
  假设现在已经实例化出了一个对象,下一次再次调用getInstance()函数实例化对象,首先要获取互斥锁,之后if判断发现对象已经实例化,此时直接返回已经实例化的对象的指针,并且解锁。要知道获取互斥锁和解锁,都会花费一定的时间,是有代价的,所以我们是不是在加锁之前,先判断对象是否已经实例化,若已实例化,则直接返回对象地址,从而省略每次的加锁操作,提高效率。代码如下:

class Singleton {
public :
    static Singleton* getInstance() {
        if (instance == nullptr) {
            std::unique_lock<std::mutex> lock(m_mutex);
            if (instance == nullptr) {
                instance = new Singleton();
            }
        }
        return instance;
    }
    
private :
    static std::mutex m_mutex;
    static Singleton* instance;
    Singleton() {};
    Singleton(Singleton &obj) = delete;
    ~Singleton() {}
};

Singleton *Singleton::instance = nullptr;
std::mutex Singleton::m_mutex;

  这样代码就完成了。但是我们思考一下,有了外层的if (instance == nullptr)里层的if (instance == nullptr)是不是就有些多余呢?其实不是的,这还是从多线程环境中举反例,若去除内层判断,假设线程A, B都通过了外层if判断,线程A拿到锁,执行完后,释放锁,随后线程B拿到锁,没有了内层if判断,将会再实例化对象,这显然是不行的。

三 饿汉模式和懒汉模式

  单例模式分为饿汉模式和懒汉模式两种,上面说的是懒汉模式(什么时候调用getInstance()函数才什么时候实例化对象,是不是有些懒?),而饿汉模式则不同,直接程序开始运行的时候就直接实例化对象,代码如下:

class Singleton {
public :
    static Singleton* getInstance() {
        return instance;
    }
    
private :
    static std::mutex m_mutex;
    static Singleton* instance;
    Singleton() {};
    Singleton(Singleton &obj) = delete;
    ~Singleton() {}
};

Singleton *Singleton::instance = new Singleton();
std::mutex Singleton::m_mutex;

  等一下,不是把构造函数私有化了吗?为什么还可以直接new Singleton(),这有问题呀!!!
  类中将属性和方法分为public, protected, private三类,表示的是类外对类内部成员的可见程度,注意是类外看类内,但是这里的instance本就是类内元素,所以自然可以调用类内的构造函数,自然也就可以new。

  单例模式总的来说:分为懒汉模式和饿汉模式,懒汉模式更加优美,需要注意加锁和两层if判断,以及相关变量和函数的私有化。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值