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

单例模式是设计模式中的一种,本文记录和总结单例模式的定义以及C++中实现单例模式的几种懒汉式方式。

参考C++ 单例模式总结与剖析

什么是单例模式

单例(Singleton)模式的特点是这个类在全局只有唯一的一个实例对象,在所有位置都可以通过该类提供的接口访问到这个唯一实例。最经典的使用场景是公用缓存,由一个线程周期性地写,由多个线程读取,此时就可以用单例模式来保证不同线程写和读的是同一个实例。

C++实现单例模式,有几个基础要点:

  1. 保证全局只有一个实例。通过把构造函数设置为private可以防止用户自己定义实例。
  2. 用户必须通过一个静态成员函数GetInstance来获取实例。静态成员函数可以在不定义对象的情况下调用。
  3. 要禁止对实例进行赋值和拷贝。
  4. 要保证线程安全。

懒汉式单例

饿汉式 vs 懒汉式

参考懒汉式单例模式与饿汉式单例

饿汉式指的是无论会不会有人调用getInstance获取实例,都会事先把这个实例给创建好,非常的勤勉、饥渴。饿汉式可以通过在类里加入一个静态成员变量来实现(注意C++的静态成员变量必须在类外初始化,下面的参考代码是java代码,所以不用担心这个问题)

public class One {
    //私有化构造方法使得该诶无法通过外部new进行实例化
    private One(){}
    //准备一个类属性,指向一个实例化对象。 因为是类属性,所以只有一个
    private static One instance = new One();

    public static One getInstance(){
        return instance;
    }
}

懒汉式指的是只有在有人调用getInstance获取实例时,才创建这个实例,可见比较懒惰,好处是没人调用时不会占用内存。

最基础的懒汉式(线程不安全、内存不安全)

#include <iostream>
// version1:
// with problems below:
// 1. thread is not safe
// 2. memory leak

class Singleton{
private:
    Singleton(){
        std::cout<<"constructor called!"<<std::endl;
    }
    Singleton(Singleton&)=delete;
    Singleton& operator=(const Singleton&)=delete;
    static Singleton* m_instance_ptr;
public:
    ~Singleton(){
        std::cout<<"destructor called!"<<std::endl;
    }
    static Singleton* get_instance(){
        if(m_instance_ptr==nullptr){
              m_instance_ptr = new Singleton;
        }
        return m_instance_ptr;
    }
    void use() const { std::cout << "in use" << std::endl; }
};

Singleton* Singleton::m_instance_ptr = nullptr;

int main(){
    Singleton* instance = Singleton::get_instance();
    Singleton* instance_2 = Singleton::get_instance();
    return 0;
}

可以看到,禁止了赋值和拷贝函数,构造函数被设置为private,只有通过静态成员函数get_instance才能获取实例。但是这里考虑的不够周全:

  • 线程不安全:当有多个线程并发调用get_instance时,都认为m_instance_ptr==nullptr而导致创建了多个实例,违背了单例原则。
  • 内存不安全:只有new,没有delete

智能指针和锁实现懒汉式(线程安全、内存安全)

智能指针和锁实现

#include <iostream>
#include <memory> // shared_ptr
#include <mutex>  // mutex

// version 2:
// with problems below fixed:
// 1. thread is safe now
// 2. memory doesn't leak

class Singleton{
public:
    typedef std::shared_ptr<Singleton> Ptr;
    ~Singleton(){
        std::cout<<"destructor called!"<<std::endl;
    }
    Singleton(Singleton&)=delete;
    Singleton& operator=(const Singleton&)=delete;
    static Ptr get_instance(){

        // "double checked lock"
        if(m_instance_ptr==nullptr){
            std::lock_guard<std::mutex> lk(m_mutex);
            if(m_instance_ptr == nullptr){
              m_instance_ptr = std::shared_ptr<Singleton>(new Singleton);
            }
        }
        return m_instance_ptr;
    }


private:
    Singleton(){
        std::cout<<"constructor called!"<<std::endl;
    }
    static Ptr m_instance_ptr;
    static std::mutex m_mutex;
};

// initialization static variables out of class
Singleton::Ptr Singleton::m_instance_ptr = nullptr;
std::mutex Singleton::m_mutex;

int main(){
    Singleton::Ptr instance = Singleton::get_instance();
    Singleton::Ptr instance2 = Singleton::get_instance();
    return 0;
}

这里对上一个基础实现版本做出了改进:

  • 用智能指针shared_ptr创建实例,当不再有指针指向这个实例时,shared_ptr会自动销毁该实例,回收其占用的空间。
  • 加锁获取实例,保证只会创建一个实例。而且这里用了双检锁的思想,先判断是否为null,然后再加锁,避免因为把加锁操作放在外面而阻塞了正常的在已有实例的情况下获取实例的情况。

局部静态变量实现懒汉式(线程安全)

#include <iostream>

class Singleton
{
public:
    ~Singleton(){
        std::cout<<"destructor called!"<<std::endl;
    }
    Singleton(const Singleton&)=delete;
    Singleton& operator=(const Singleton&)=delete;
    static Singleton& get_instance(){
        static Singleton instance;
        return instance;

    }
private:
    Singleton(){
        std::cout<<"constructor called!"<<std::endl;
    }
};

int main(int argc, char *argv[])
{
    Singleton& instance_1 = Singleton::get_instance();
    Singleton& instance_2 = Singleton::get_instance();
    return 0;
}


通过在get_instance函数中引入静态变量,达到了在不使用智能指针和锁的情况下,也能够保证线程安全和内存安全的效果,

  • C++11标准中的Magic Static特性保证线程安全,如果当变量在初始化的时候,并发同时进入声明语句,并发线程将会阻塞等待初始化结束。
  • 没有使用指针来new对象,没有内存泄漏的问题。

由于静态局部变量在程序执行到该对象的声明处时才被首次初始化,所以这也是一种懒汉式。(饿汉式用的是类的静态成员变量,不属于这个类,)

参考C/C++ 中 static 的用法全局变量与局部变量 C++静态变量的生存期

  • 4
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值