前言
个人学习笔记
之前的介绍过关于单例模式的例子,其中对象是由_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;
}