C++ 单例模式

什么是单例模式?

  一种创建型的设计模式,该模式的主要目的就是确保某个类有且仅有一个实例存在。

 

单例模式有三个关键点:

  1、单例类只能有一个实例。

    为此,单例类只能提供私有的构造函数,即保证不能随意创建该类的实例。
  2、单例类必须自己创建自己的唯一实例。

    因为构造函数是私有的,其他对象不能创建单例类的实例,只能是单例类自己来创建。
  3、单例类必须给所有其他对象提供这一实例。

    外界需要获取并使用这个单例类的实例,但是由于该类的构造函数是私有的,外界无法通过new去获取它的实例,那么就必须提供一个静态的公有方法,该方法创建或者获取它本身的静态私有对象并返回。

 

单例模式有两种实现方式:

  懒汉式:故名思义,懒汉很懒只有饿了才会去找吃的。也就是说,只有在需要使用的时候才会去实例化。

  饿汉式:饿了肯定要饥不择食。在单例类定义的时候就进行实例化。

 

懒汉式单例模式

//Singleton.h
#pragma once

class Singleton {
public:
    static Singleton* getInstance();

private:
    Singleton();
    Singleton(const Singleton&);
    Singleton& operator=(const Singleton&);

    static Singleton* instance_;
};

//Singleton.cpp
#include <iostream>
#include "Singleton.h"

Singleton* Singleton::instance_ = NULL;

Singleton::Singleton() {
}

Singleton::Singleton(const Singleton&) {
}

Singleton &
Singleton::operator=(const Singleton&) {
}

Singleton *
Singleton::getInstance() {
    if (NULL == instance_) {
        instance_ = new Singleton();
    }

    return instance_;
}

  (1)默认构造函数是私有的,外部不能进行单例类的实例化;

  (2)拷贝构造函数和赋值运算符也是私有的,以禁止拷贝和赋值;

  (3)具有一个私有的静态成员指针 instance_,指向唯一的实例;

  (4)提供一个公有的静态成员函数用于返回实例,如果实例为NULL,则进行实例化。

 

饿汉式单例模式

//Singleton.h
#pragma once

class Singleton {
public:
    static Singleton* getInstance();

private:
    Singleton();
    Singleton(const Singleton&);
    Singleton& operator=(const Singleton&);

    static Singleton* instance_;
};

//Singleton.cpp
#include <iostream>
#include "Singleton.h"

Singleton* Singleton::instance_ = new Singleton();

Singleton::Singleton() {
}

Singleton::Singleton(const Singleton&) {
}

Singleton &
Singleton::operator=(const Singleton&) {
}

Singleton *
Singleton::getInstance() {
    return instance_;
}

   与懒汉式单例模式不同之处是,在全局作用域进行单例类的实例化,并用此实例初始化单例类的静态成员指针instance_。

 

线程安全问题

  懒汉式:如果有两个线程同时获取单例类的实例,都发现实例不存在,因此都会进行实例化,就会产生两个实例都要赋值给instance_,这是严重的错误。为了解决这个问题,就要考虑加锁。

  线程安全的懒汉式,修改获取实例的方法如下:

Singleton *
Singleton::getInstance() {
    lock();        //上锁
    if (NULL == instance_) {
        instance_ = new Singleton();
    }
    unlock();

    return instance_;
}

  但这个获取实例的方法存在性能问题,每次获取实例的时候都要先上锁,之后再解锁,如果有很多线程的话,可能会造成大量线程的阻塞。改进后的实现如下:

Singleton *
Singleton::getInstance() {
    if (NULL == instance_) {
        lock();        //上锁
        if (NULL == instance_) {
            instance_ = new Singleton();
        }
        unlock();
    }

    return instance_;
}

  绝大多数情况下,获取实例时都是直接返回实例,这时候不会涉及到上锁、解锁的问题。只有在没有实例的时候,才会涉及上锁、解锁,这种情况是很少的。这个获取实例的方法对性能几乎无影响。

  饿汉式:程序运行初期就进行了单例类实例化,不存在上述的线程安全问题。

 

对象释放问题

   上边的程序中只有new,却没有delete,也就是说只有内存申请而没有内存释放,会不会有内存泄漏?

  答:一般情况下,单例类的实例都是常驻内存的,一直存在于进程的生命周期,因此不需要手动释放。如果的确需要释放实例占用的内存,一定不能在单例类的析构函数中进行delete操作,这样会造成无限循环,可以考虑增加一个destroy方法用于释放内存,或者在单例类中定义一个内嵌的垃圾回收类,详情请参考最后两个参考链接。

 

参考与鸣谢:

  https://www.cnblogs.com/cxjchen/p/3148582.html
  https://www.cnblogs.com/qiaoconglovelife/p/5851163.html
  https://blog.csdn.net/stpeace/article/details/68953096
  https://blog.csdn.net/zhanghuaichao/article/details/79459130
  https://blog.csdn.net/chenyingying_/article/details/83029600
  https://www.cnblogs.com/xzy1210/p/3849253.html

posted on 2019-06-06 18:09  泣血  阅读(15148)  评论(0编辑  收藏  举报

导航