单例模式(C++)

前言

  单例模式在实际应用中被广泛使用,其意图是保证一个类只有一个实例。在网上查阅了部分资料,发现用C++写单例模式考虑的问题挺多的,当然目前C++11的一些特性基本解决了原来出现的问题(C#和Java自身的机制保证基本不会出现C++中的问题),这里记录下。

单例模式实现

1. 概要

单例模式概要

2. 懒汉式

//懒汉式(存在内存泄漏问题,且非线程安全)
class Singleton_lazy
{
public:
    static Singleton_lazy* getInstance(){
        if(instance == nullptr){
            instance = new Singleton_lazy();
        }
        return instance;
    }
private:
    Singleton_lazy(){}//构造函数私有
    ~Singleton_lazy(){}
    Singleton_lazy(const Singleton_lazy&){} //拷贝构造私有
    Singleton_lazy &operator=(const Singleton_lazy&){} //赋值构造私有
    
    static Singleton_lazy* instance; //实例静态化
};
//初始化懒汉模式的静态成员变量
Singleton_lazy *Singleton_lazy::instance = nullptr;

  上述方法中存在着内存泄漏的问题(主要体现在Singleton_lazy::instance指针释放),解决方法有两种:
  (1)使用智能指针(C++11特性)
  (2)使用静态的嵌套类对象
  对于第二种使用嵌套类的方法,代码如下:

//懒汉式(使用静态的嵌套类方法避免内存泄漏,仍然非线程安全)
class Singleton_lazy
{
public:
    static Singleton_lazy* getInstance(){
        if(instance == nullptr){
            instance = new Singleton_lazy();
        }
        return instance;
    }
private:
    Singleton_lazy(){}//构造函数私有
    ~Singleton_lazy(){}
    Singleton_lazy(const Singleton_lazy&){} //拷贝构造私有
    Singleton_lazy &operator=(const Singleton_lazy&){} //赋值构造私有
    
    static Singleton_lazy* instance; //实例静态化
    
 private:
    //使用嵌套的静态类(新增部分)
    class Deletor{
      public:
        ~Deletor(){
            if(Singleton_lazy::instance != NULL){
                delete Singleton_lazy::instance;
            }//if
        } //~Deletor
    };//class
    static Deletor deletor;
};
//初始化懒汉模式的静态成员变量
Singleton_lazy *Singleton_lazy::instance = nullptr;

  当然,如果要考虑线程安全,需要利用同步机制进行处理,其中的一种同步的方法是双重校验锁(DCL: Double-Checked Locking Pattern),代码如下:

#include <mutex>
//懒汉模式的线程安全方法
std::mutex mt;
class Singleton_lazySafeThread
{
public:
    static Singleton_lazySafeThread* getInstance(){
        if(instance == nullptr){
            mt.lock();
            if(instance == nullptr){
                 instance = new Singleton_lazySafeThread();
            }
            mt.unlock();

        }
        return instance;
    }
private:
    Singleton_lazySafeThread(){}//构造函数私有
    ~Singleton_lazySafeThread(){}
    Singleton_lazySafeThread(const Singleton_lazySafeThread&){} //拷贝构造私有
    Singleton_lazySafeThread &operator=(const Singleton_lazySafeThread&){} //赋值构造私有

    static Singleton_lazySafeThread* instance; //实例静态化
};
//初始化懒汉模式的静态成员变量
Singleton_lazySafeThread *Singleton_lazySafeThread::instance = nullptr;//懒汉模式(双重校验锁,线程安全)

  但是,看网上的资料说,在C++11出来之前,由于编译器优化的原因(需要靠内存屏障去解决),导致instance虽然已经不等于nullptr,但是此时instance并没有初始化,此时会出现不同步的严重问题,解决方法除了内存屏障的指令,还可以用原子操作保护内存同步(C++11提供了这种机制)。
  另外,在C++11中,懒汉模式有一种非常优雅的写法,能够解决内存同步和线程的问题(其实就是返回一个引用,不是指针),代码如下:

class Singleton
{
private:
	Singleton() { };
	~Singleton() { };
	Singleton(const Singleton&);
	Singleton& operator=(const Singleton&);
public:
	static Singleton& getInstance() //返回引用
        {
		static Singleton instance;
		return instance;
	}
};

  返回引用的好处是没有没有用到指针,也就是不用考虑手动delete。

3. 饿汉式

//饿汉模式(立即初始化,没有线程安全问题),但是注意在合适的时候delete(如果用到指针的话)
//当然,下面的例子是用的返回引用的方式
class Singleton_eager 
{
public:
    static Singleton_eager& getInstance(){//没有用指针
         return instance;
    }
private:
    Singleton_eager(){}//构造函数私有
    ~Singleton_eager(){}
    Singleton_eager(const Singleton_eager&){} //拷贝构造私有
    Singleton_eager &operator=(const Singleton_eager&){} //赋值构造私有
    
    static Singleton_eager instance; //实例静态化
};
//初始化饿汉模式的静态成员变量
Singleton_eager Singleton_eager::instance;

  当然,这种饿汉模式也存在一些潜在的问题,主要体现在实例化对象的顺序可能会导致实例化对象不成功。

4. 设计模式总结

  总之,C++11之后,内存模型方面做了不少的优化,推荐使用返回引用的方式。
  懒汉模式和饿汉模式的场景可以按照以下原则:
  (1)懒汉模式时间换取空间,也就是不要万不得已,不去实例化对象,适用于访问量不大,但是追求效率的场景下
  (2)饿汉模式空间换取时间,在访问量大或线程较多的场景下用

  另外,C++中的一些设计模式的总结导航,可以参考这里(设计模式总结)。

参考资料

[1] https://zhuanlan.zhihu.com/p/37469260
[2] https://www.cnblogs.com/chengjundu/p/8473564.html

猜你喜欢

转载自www.cnblogs.com/treature/p/13198790.html