C++单例模式(线程安全、内存释放)

一、懒汉模式:

即第一次调用该类实例的时候才产生一个新的该类实例,并在以后仅返回此实例。
需要用锁,来保证其线程安全性:原因:多个线程可能进入判断是否已经存在实例的if语句,从而non thread safety
使用double-check来保证thread safety。但是如果处理大量数据时,该锁才成为严重的性能瓶颈。

1、静态成员实例的懒汉模式:

饿汉方式不论是否需要使用该对象都将其定义出来,可能浪费了内存,或者减慢了程序的启动速度。所以使用懒汉模式进行优化,懒汉模式即延迟构造对象,在第一次使用该对象的时候才进行new该对象。
而懒汉模式会存在线程安全问题,最出名的解决方案就是Double-Checked Locking Pattern (DCLP)。使用两次判断来解决线程安全问题并且提高效率。

class Singleton  
{ 
public:  
  	static Singleton* getInstance() {
        //第一个检查,如果只是读操作,就不许用加锁
        if (m_pInstance == nullptr) {
            std::lock_guard<std::mutex> lck(m_mutex);
            //第二个检查,保证只有一个
            if (m_pInstance == nullptr) {
                m_pInstance = new Singleton;
            }
        }
        return m_pInstance;
    }
    static void delInstance(){ // 为了多线程安全,本人觉得释放操作也要做 double-check
    	if(m_pInstance != nullptr) 
    	{
    		std::lock_guard<std::mutex> lck(m_mutex);
    		if(m_pInstance != nullptr) 
    		{
            	delete m_pInstance;
            	m_pInstance = nullptr;
        	}
    	}        
    };
    
private:
 	Singleton(){
        std::cout << "Singleton Hello" << std::endl;
    };
    ~Singleton() { // 私有化 可以避免 直接 delete s1 ,必须 使用 delInstance
        std::cout << "Singleton Bye" << std::endl;
    }
    
    static Singleton* m_pInstance;
    static std::mutex m_mutex;
};  
Singleton* Singleton::m_pInstance = nullptr;
std::mutex Singleton::m_mutex;

Double-Checked Locking Pattern存在的问题:
Double-Checked Locking Pattern (DCLP)实际上也是存在严重的线程安全问题。Scott Meyers and 和Alexandrescu写的一篇文章里面专门分析了这种解决方案的问题C++ and the Perils of Double-Checked Locking。文章截图:
在这里插入图片描述
如上代码,对于 check second 语句内是一个写操作,我们用Lock(例如:mutex)来保护m_pInstance这个变量。但是check first 语句是一个读操作,if (m_pInstance == nullptr),这个语句是用来读取m_pInstance这个变量,而这个读操作是没有锁的。所以在多线程情况下,这种写法明显存在线程安全问题。

《C++ and the Perils of Double-Checked Locking》这篇文章中提到:
m_pInstance = new Singleton;
这条语句实际上做了三件事,第一件事申请一块内存,第二件事调用构造函数,第三件是将该内存地址赋给instance_。

但是不同的编译器表现是不一样的。可能先将该内存地址赋给m_pInstance,然后再调用构造函数。这是线程A恰好申请完成内存,并且将内存地址赋给m_pInstance,但是还没调用构造函数的时候。线程B执行到语句1,判断m_pInstance此时不为空,则返回该变量,然后调用该对象的函数,但是该对象还没有进行构造。

不能自动析构问题
上述代码还有一个问题,当程序使用完该单例,需要手动去释放该单例管理的资源,比如定义一个公共接口delInstance()内部delete m_pInstance。如果不去手动释放管理的资源(例如加载的文件句柄等),虽然程序结束会释放这个静态成员指针变量的内存(只是指针变量本身的内存,而不是指针所指对象的内存),但是并没有调用其指向对象的析构函数去关闭这些管理的资源句柄等(注意:不能在析构函数中使用delete m_pInstance,这会导致一直析构和delete操作循环嵌套下去 )。解决办法就是能自动析构这个单例。方法如下

2、内部静态实例的懒汉模式

这里需要注意的是,C++0X以后,要求编译器保证内部静态变量的线程安全性,可以不加锁。但C++ 0X以前,仍需要加锁。

class SingletonInside  
{  
private:  
    SingletonInside(){}  
public:  
    static SingletonInside* getInstance()  
    {  
        Lock(); // not needed after C++0x  
        static SingletonInside instance;  
        UnLock(); // not needed after C++0x  
        return instance;   
    }  
};  

二、饿汉模式:

即无论是否调用该类的实例,在程序开始时就会产生一个该类的实例,并在以后仅返回此实例。
由静态初始化实例保证其线程安全性,WHY?因为静态实例初始化在程序开始时进入主函数之前就由主线程以单线程方式完成了初始化,不必担心多线程问题。
故在性能需求较高时,应使用这种模式,避免频繁的锁争夺。

1. 静态成员实例指针变量
class SingletonStatic  
{  
private:  
    static const SingletonStatic* m_pInstance;  
    SingletonStatic(){}  
public:  
    static SingletonStatic* getInstance()  
    {  
        return m_pInstance;  
    }  
};  

//外部初始化 before invoke main  
const SingletonStatic* SingletonStatic::m_pInstance= new SingletonStatic;  

m_pInstance指向的空间什么时候释放呢?更严重的问题是,该实例的析构函数什么时候执行?

如果在类的析构行为中有必须的操作,比如关闭文件,释放外部资源,那么上面的代码无法实现这个要求。我们需要一种方法,正常的删除该实例。
可以在程序结束时调用GetInstance(),并对返回的指针掉用delete操作。这样做可以实现功能,但不仅很丑陋,而且容易出错。因为这样的附加代码很容易被忘记,而且也很难保证在delete之后,没有代码再调用GetInstance函数。
一个妥善的方法是让这个类自己知道在合适的时候把自己删除,或者说把删除自己的操作挂在操作系统中的某个合适的点上,使其在恰当的时候被自动执行。
我们知道,程序在结束的时候,系统会自动析构所有的全局变量。事实上,系统也会析构所有的类的静态成员变量,就像这些静态成员也是全局变量一样。利用这个特征,我们可以在单例类中定义一个这样的静态成员变量,而它的唯一工作就是在析构函数中删除单例类的实例。如下面的代码中的CGarbo类(Garbo意为垃圾工人):

2. 静态成员实例指针变量+内嵌类静态成员实例变量
// Example program
#include <iostream>
#include <string>
using namespace::std;

class CSingleton  
{  
//其他成员  
public:  
    static CSingleton* GetInstance(){return m_pInstance;};  
private:  
    CSingleton(){cout<<"CSingleton ctor"<<endl;}; 
    ~CSingleton(){cout<<"CSingleton dtor"<<endl;};  
    static CSingleton * m_pInstance;  
    
    class CGarbo //它的唯一工作就是在析构函数中删除CSingleton的实例  
    {  
    public:  // CGarbo的构造和析构 必须声明成public,否则编译失败!!!
        ~CGarbo()  
        {  
            if( CSingleton::m_pInstance )
            {
                cout<<"CGarbo dtor"<<endl;
                delete CSingleton::m_pInstance;
                CSingleton::m_pInstance = nullptr;
            }      
        }  
	};
    static CGarbo Garbo; //定义一个静态成员,程序结束时,系统会自动调用它的析构函数  
};  

CSingleton::CGarbo CSingleton::Garbo;  // 一定要初始化,不然程序结束时不会析构garbo  
CSingleton *CSingleton::m_pInstance = new CSingleton();

int main()
{
    std::cout<<"main() enter!!!"<<std::endl;
    std::cout<<"main() exit!!!"<<std::endl;
	return 0;
}

执行结果:

CSingleton ctor
main() enter!!!
main() exit!!!
CGarbo dtor
CSingleton dtor

类CGarbo被定义为CSingleton的私有内嵌类,以防该类被在其他地方滥用。
程序运行结束时,系统会调用CSingleton的静态成员Garbo的析构函数,该析构函数会删除单例的唯一实例。
使用这种方法释放单例对象有以下特征:
在单例类内部定义专有的嵌套类;
在单例类内定义私有的专门用于释放的静态成员;
利用程序在结束时析构全局变量的特性,选择最终的释放时机;
使用单例的代码不需要任何操作,不必关心对象的释放。
具体代码如下:

#include <iostream>
using namespace std;  

class Singleton   
{   
public:  
    static Singleton *GetInstance(){return m_pInstance;};  
private:  
    Singleton()  
    {  
        cout << "Singleton ctor" << endl;  
    }  
    ~Singleton()  
    {  
        cout << "Singleton dtor" << endl;  
    }  
    static Singleton *m_pInstance;  
    class Garbo  
    {  
    public:  
        ~Garbo()  
        {  
            if (Singleton::m_pInstance)  
            {  
                cout << "Garbo dtor" << endl;  
                delete Singleton::m_pInstance; 
                Singleton::m_pInstance = nullptr;
            }  
        }  
    };  
    static Garbo garbo;  
};   
Singleton::Garbo Singleton::garbo;  // 一定要初始化,不然程序结束时不会析构garbo  
Singleton *Singleton::m_pInstance = new Singleton();  


int main()  
{
    cout<<"main enter!!!"<<endl;
    Singleton *p1 = Singleton::GetInstance();  
    Singleton *p2 = Singleton::GetInstance();  
    if (p1 == p2)  cout << "p1 == p2" << endl; 
    cout<<"main exit!!!"<<endl;
    return 0;  
} 

执行结果:

Singleton ctor
main enter!!!
p1 == p2
main exit!!!
Garbo dtor
Singleton dtor
3. 静态成员实例变量

参照上面 “1. 静态成员实例指针变量” 代码,如果将单例类的静态成员变量SingletonStatic* m_pInstance由指针改成实例对象SingletonStatic m_instance 会不会更好呢!这完美的规避了 new 和 delete 的配套使用限制,我们不在需要烦恼m_pInstance指针的释放,而且程序在结束的时候,系统会自动析构所有的类的静态成员变量(包括 m_instance):

#include <iostream>
using namespace std;  

class SingletonStatic  
{  
private:  
    static SingletonStatic m_instance;
    SingletonStatic(){cout<<"SingletonStatic ctor"<<endl;};
    ~SingletonStatic(){cout<<"SingletonStatic dtor"<<endl;};
public:  
    static SingletonStatic& getInstance()  
    {  
        return m_instance; 
    }; 
};  
//外部初始化 before invoke main  
SingletonStatic SingletonStatic::m_instance;  // or = SingletonStatic();


int main()  
{
    cout<<"main enter!!!"<<endl;
    cout<<"main exit!!!"<<endl;
    return 0;  
}

执行结果:

SingletonStatic ctor
main enter!!!
main exit!!!
SingletonStatic dtor

需要注意的是,如果 m_instance 被声明称 const 静态成变量,那么getInstance()的返回类型必须 const 引用。否则类型不匹配会导致 编译器报错 invalid initialization of reference of type ‘SingletonStatic&’ from expression of type ‘const SingletonStatic’。如下面代码中都声明成 const 类型才能编译通过。

class SingletonStatic  
{  
private:  
    static const SingletonStatic m_instance;  
    SingletonStatic(){cout<<"SingletonStatic ctor"<<endl;};
    ~SingletonStatic(){cout<<"SingletonStatic dtor"<<endl;};
public:  
    static const SingletonStatic& getInstance()  
    {  
        return m_instance;  
    }; 
};  
//外部初始化 before invoke main  
const SingletonStatic SingletonStatic::m_instance;  // or = SingletonStatic();

参考:

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值