C++基础之单例模式自动释放


前言

个人学习笔记


       之前的介绍过关于单例模式的例子,其中对象是由_pInstance指针来保存,通过new创建的对象并没有进行释放,是因为单例模式之后没有其他代码需要执行,程序会立马结束,操作系统会自行回收相关资源,但对于后面有相关代码需要执行的程序来说,这种操作就会造成内存泄漏。有没有什么方式可以让对象自动释放?这样既可以避免内存泄漏,又可以让程序员不用关注对象释放的问题。在涉及到自动的问题时,我们很自然的可以想到:对象被销毁时,会自动调用其析构函数。利用这一特性,我们可以解决这一问题,这里介绍4种方式对单例模式进行自动释放:友元形式、内部类加静态数据成员形式、atexit形式、pthread_once形式。

一、友元形式

       创建一个新类AutoRelease,作为Singleton的友元,创建该类的栈对象,由于编译器会自动释放栈对象,在释放栈对象时会执行析构函数,这样执行析构函数的同时顺便把单例模式的对象给释放了,这样就可以达到目的了。
代码如下(示例):

#include <iostream>

using std::cout;
using std::endl;
class AutoRelease;

//1、友元形式
class Singleton
{
    friend AutoRelease;
public:
    static Singleton * getInstance()
    {
        if(nullptr == _pInstance)
        {
            _pInstance = new Singleton();
        }
        return _pInstance;
    }
    //不需要主动调用来释放对象了
    static void destroy()
    {
        if(_pInstance)
        {
            cout << "destroy()" << endl;
            delete _pInstance;
            _pInstance = nullptr;
        }
    }
private:
    Singleton()
    {
        cout << "Singleton()" << endl;
    }
    ~Singleton()
    {
        cout << "~Singleton()" << endl;
    }
private:
    static Singleton * _pInstance;
};
class AutoRelease
{
public:
    AutoRelease()
    {
        cout << "AutoRelease()" << endl;
    }
    ~AutoRelease()
    {
        cout << "~AutoRelease()" << endl;
        if(Singleton::_pInstance)//作为友元类,静态数据成员可以通过类名加作用域限定符获取
        {
            delete  Singleton::_pInstance;
            Singleton::_pInstance = nullptr;
        }
    }
};
Singleton *Singleton::_pInstance = nullptr;
int main()
{
    Singleton *s1 = Singleton::getInstance();
    Singleton *s2 = Singleton::getInstance();
    cout << "&s1 = " << s1 << endl;
    cout << "&s2 = " << s2 << endl;
    /* s1->destroy(); */
    /* s2->destroy(); */
    /* Singleton::destroy(); */
    AutoRelease ar;//创建栈对象,栈对象销毁的同时,顺便把Singeleton对象进行销毁,不用调用destroy函数,自动把单例对象释放
    return 0;
}

二、内部类+静态数据成员形式

       除了友元,还可以把AutoRelease作为Singleton的内部类,用public修饰时,可以直接在外面创建AutoRelease的栈对象,其作用原理与友元形式类似,因为AutoRelease只为Singleton服务,所以AutoRelease要用private修饰,用private修饰后,AutoRelease对象就不能在Singleton类外面创建,所以只能在Singleton类里面创建,作为Singleton的普通数据成员,在Singleton里创建AutoRelease对象时,会调用AutoRelease构造函数,而此时的AutoRelease对象并不是栈对象,所以不会自动执行析构函数,需要Singleton创建的类对象销毁时才会回收数据成员,这时才会执行AutoRelease的析构函数,进而再回收Singleton的对象,而Singleton创建的单例对象就是靠AutoRelease才能释放掉,这里就陷入了一个死循环,所以就不能创建AutoRelease对象作为Singleton的普通数据成员,也就是说Singleton对象不能拥有AutoRelease对象作为普通数据成员,所以需要把AutoRelease创建的对象从Singleton创建的对象中拿出来,放到全局/静态区的位置,也就是让AutoRelease创建的对象作为Singleton的静态数据成员,不仅仅被某个Singleton对象拥有,它是一个全局的,不受某一个Singleton对象控制,又因为处于全局/静态区的对象或变量会随着进程的结束而进行销毁,这时就会执行AutoRelease的析构函数,进而把_pInstance给delete掉,这时就会执行Singleton的析构函数,销毁释放Singleton单例模式创建的对象,最终达到目的。
代码如下(示例):

#include <iostream>

using std::cout;
using std::endl;
class AutoRelease;

//2、内部类 + 静态数据成员
class Singleton
{
public:
    static Singleton * getInstance()
    {
        if(nullptr == _pInstance)
        {
            _pInstance = new Singleton();
        }
        return _pInstance;
    }
    static void destroy()
    {
        if(_pInstance)
        {
            cout << "destroy()" << endl;
            delete _pInstance;
            _pInstance = nullptr;
        }
    }
private:
    class AutoRelease
    {
    public:
        AutoRelease()
        {
            cout << "AutoRelease()" << endl;
        }
        ~AutoRelease()
        {
            cout << "~AutoRelease()" << endl;
            if(_pInstance)//内部类可以直接使用_pInstance
            {
                delete  _pInstance;
                _pInstance = nullptr;
            }
        }
    };
private:
    Singleton()
    {
        cout << "Singleton()" << endl;
    }
    ~Singleton()
    {
        cout << "~Singleton()" << endl;
    }
private:
    static Singleton * _pInstance;
    static AutoRelease _ar;//不是栈对象
};
Singleton *Singleton::_pInstance = nullptr;
Singleton::AutoRelease Singleton::_ar;//放到全局静态的位置,编译器会自动赋一个初值
int main()
{
    Singleton *s1 = Singleton::getInstance();
    Singleton *s2 = Singleton::getInstance();
    cout << "&s1 = " << s1 << endl;
    cout << "&s2 = " << s2 << endl;
    /* Singleton::AutoRelease ar; */
    /* s1->destroy(); */
    /* s2->destroy(); */
    /* Singleton::destroy(); */
    cout << sizeof(Singleton) << endl;//=1
    return 0;
}

三、atexit形式

       这里需要借助一个C的函数atexit(),atexit函数会注册一个函数,注册的函数会在进程正常结束的时候调用,通过atexit注册多次就会调用多次。这时需要考虑atexit的位置,考虑到Singleton单例模式创建的对象只能被销毁一次,所以atexit()最好是放到单例模式创建的作用域内。但是在多线程环境下,上面的代码都是有缺陷的,因为if(nullptr==_pInstance)判断语句对于有几个线程的情况下,可能会同时满足if判断语句中的内容,然后创建多个Singleton对象,这样不仅不符合单例模式(一个类有且只能有一个对象),而且释放的时候不会把多线程创建的多个对象全部销毁释放掉,从而造成内存泄漏,这种情况下,对于多线程环境下是不安全的。出现这种情况是因为,在进入if判断语句之前,_pInstance的值为空,所以这里我们可以在_pInstance初始化的时候赋一个值,这样下次进入getInstance()时,_pInstance已经有值了,即使在多线程环境下,也不会创建多个对象,这样解决了多线程环境下不安全的问题,这就对应一种模式:饿汉模式。
代码如下(示例):

#include <stdlib.h>
#include <iostream>

using std::cout;
using std::endl;
class AutoRelease;

//3、atexit + 饿汉模式
//可以解决多线程环境下不安全的问题
class Singleton
{
public:
    static Singleton * getInstance()
    {
        if(nullptr == _pInstance)
        {
            _pInstance = new Singleton();
            atexit(destroy);
        }
        return _pInstance;
    }
    static void destroy()
    {
        cout << "destroy()" << endl;
        if(_pInstance)
        {
            delete _pInstance;
            _pInstance = nullptr;
        }
    }
private:
    Singleton()
    {
        cout << "Singleton()" << endl;
    }
    ~Singleton()
    {
        cout << "~Singleton()" << endl;
    }
private:
    static Singleton * _pInstance;
};
/* Singleton *Singleton::_pInstance = nullptr;//饱汉模式(懒汉模式) */
Singleton *Singleton::_pInstance = getInstance();//饿汉模式
int main()
{
    Singleton *s1 = Singleton::getInstance();
    Singleton *s2 = Singleton::getInstance();
    cout << "&s1 = " << s1 << endl;
    cout << "&s2 = " << s2 << endl;
    return 0;
}

四、pthread_once形式

       其实在linux系统中,有一个函数可以保证多线程环境下安全性的问题,它就是pthread_once(),完整形式:int pthread_once(pthread_once_t *once_control, void (*init_routine)(void)),该函数使用初值为PTHREAD_ONCE_INIT的once_control变量保证init_routine()函数在本进程执行序列中仅执行一次。在多线程编程环境下,尽管pthread_once()调用会出现在多个线程中,但init_routine()函数仅执行一次,究竟在哪个线程中执行是不定的,是由内核调度来决定。所以在这种情况下,即使在多线程环境下,选择饱汉模式或者饿汉模式都都可以,因为init_routine()函数只会执行一次。注意一点该形式只能在linux系统下使用,跨平台性不高。在linux系统通过g++编译的时候需要加上线程库-lpthread,其中这个p是POSIX,它是linux下的一个标准。
代码如下(示例):

#include <stdlib.h>
#include <pthread.h>
#include <iostream>

using std::cout;
using std::endl;
class AutoRelease;

//4、pthread_once形式
//可以解决多线程环境下不安全的问题
class Singleton
{
public:
    static Singleton * getInstance()
    {
    	//getInstance为静态函数,所以_once、init也必须设置为静态,否则在getInstance中不能访问
        pthread_once(&_once, init);//_once一旦初始化后,即使在多线程环境下,init()函数仅执行一次
        return _pInstance;
    }
    static void init()
    {
        _pInstance = new Singleton();
        atexit(destroy);
    }
    static void destroy()
    {
        cout << "destroy()" << endl;
        if(_pInstance)
        {
            delete _pInstance;
            _pInstance = nullptr;
        }
    }
private:
    Singleton()
    {
        cout << "Singleton()" << endl;
    }
    ~Singleton()
    {
        cout << "~Singleton()" << endl;
    }
private:
    static Singleton * _pInstance;
    static pthread_once_t _once;
};
Singleton *Singleton::_pInstance = nullptr;//饱汉模式(懒汉模式)
/* Singleton *Singleton::_pInstance = getInstance();//饿汉模式 */
pthread_once_t Singleton::_once = PTHREAD_ONCE_INIT;
int main()
{
    Singleton *s1 = Singleton::getInstance();
    Singleton *s2 = Singleton::getInstance();
    cout << "&s1 = " << s1 << endl;
    cout << "&s2 = " << s2 << endl;
    cout << PTHREAD_ONCE_INIT << endl;//=0
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值