C++学习笔记(十)——单例模式的三种实现
单例模式的实现目的就是只创建的多个对象都只对应与一个实体对象。本篇将介绍实现线程安全的可自动回收的单例模式的三种方法:
1、 嵌套类+饱汉式实现单例模式;
2、 atexit+ 饱汉式实现单例模式;
3、 pthread_once+atexit实现单例模式;
什么叫做线程安全
用以前的单例模式作为示例来说明问题,代码示例(代码10-1)如下:
class Singleton
{
public:
static Singleton * getInstance()
{
if(NULL == _pInstance)
{
_pInstance = new Singleton;
}
return _pInstance;
}
static void destroy()
{
if(NULL != _pInstance)
{
delete _pInstance;
}
}
private:
Singleton()
{}
~Singleton()
{}
static Singleton * _pInstance;
};
Singleton * Singleton::_pInstance = NULL;
int main()
{
Singleton single; //报错,因为构造函数在private里
Singleton * pInst1 = Singleton::getInstance();
Singleton * pInst2 = Singleton::getInstance();
cout << “pInst1 = ” << pInst1 << endl;//pInst1与pInst2地址一样
cout << “pInst2 = ” << pInst2 << endl;
pInst1->destroy();
return 0;
}
这个单例模式的实现有个缺点:不是线程安全的。具体来说就是存在一个可能:多个线程同时运行到了getInstance的判断语句(if(NULL == _pInstance)
)时,由于_pInstance=NULL
。所以这些线程会进入if语句中,这就会创建多个Singleton实例。所以实现线程安全的单例模式需要对getInstance的判断语句(if(NULL == _pInstance)
)做一定的处理。此外,下面提供了三种处理办法。
嵌套类+饱汉式实现单例模式
代码示例(代码10-2):
///
/// @file Singleton_hard.cc
/// @date 2019-02-17 21:37:47
///
#include <iostream>
using std::cout;
using std::endl;
class Singleton
{
public:
static Singleton * getInstance()
{
if(NULL == _pInstance)
{
_pInstance = new Singleton();
}
return _pInstance;
}
private:
class AutoRelease //将AutoRelease放到private是希望不让其在类外被使用
{
public:
AutoRelease()
{
cout << "AutoRelease()" << endl;
}
~AutoRelease()
{
if(NULL != _pInstance)
{
delete _pInstance;
cout << "AutoRelease->delete success!" << endl;
}
}
};
Singleton()
{
cout << "Singleton()" << endl;
}
~Singleton()
{
cout << "~Singleton()" << endl;
}
static Singleton * _pInstance;
static AutoRelease _auto;//没有_pauto就不会自动删除,为什么需要这个_auto?
//_auto与_pInstance一起放在了全局变量区(静态区)中。而让程序执行完毕之后
//就会自动销毁全局静态区,这是就销毁了_auto,当销毁_auto时,就会自动执行
//析构函数。
};
Singleton * Singleton::_pInstance = getInstance();//饱汉式实现单例模式,可以保证线程安全
Singleton::AutoRelease Singleton::_auto;
int main()
{
// Singleton single1;//这样写会报错:原因是构造和析构函数都放在了private内,但是,Singleton single1();这样写就可以,不知道为什么??
Singleton * pInst1 = Singleton::getInstance();
Singleton * pInst2 = Singleton::getInstance();
cout << "pInst1 = " << pInst1 << endl;
cout << "pInst2 = " << pInst2 << endl;
return 0;
}
对上面的代码需要做出的解释有:1、为什么饱汉式可以实现线程安全?2、为什么嵌套类可以实现线程的自动回收?
为什么饱汉式可以实现线程安全
首先要明确什么是饱汉式:在程序运行之前所设定的前提条件就能够使得之后要运行的程序全都(不)满足判断条件的即为饱汉式(个人理解并未有得到印证)。在代码10-1中可以看到。我在全局变量区中就给静态变量赋了初始值。所以,之后的所有线程当调用getInstance静态函数时都不满足判断条件而跳过了```_pInstance = new Singleton``。这就实现了线程安全。
`为什么嵌套类可以实现线程的自动回收
嵌套类实现对单例模式的自动回收的实现是这样的:在创建单例模式时会自动创建嵌套类,当单例模式使用完后会自动调用嵌套类的析构函数实现对申请堆空间的自动回收。其技术要点有以下几点:
- 嵌套类不可能在类外被创建,不然会造成单例的多次删除;
- 嵌套类只能被创建一次,因为其要实现对单例模式的回收所以必须存在且只有一个;
- 嵌套类要与
_pInstance
一起放在静态区中,程序执行完毕之后就会自动销毁全局静态区,这就销毁了嵌套类的对象,当销毁嵌套对象时,就会自动执行析构函数。
atexit+ 饱汉式实现单例模式
实例代码(代码10-3):
///
/// @file Singleton_atexit.cc
/// @date 2019-02-17 21:37:47
///
#include <stdlib.h>
#include <iostream>
using std::cout;
using std::endl;
class Singleton
{
public:
static Singleton * getInstance()
{
if(NULL == _pInstance)
{
_pInstance = new Singleton();
atexit(destory);
}
return _pInstance;
}
static void destory()
{
if(NULL != _pInstance)
{
delete _pInstance;
cout << "AutoRelease->delete success!" << endl;
}
}
private:
Singleton()
{
cout << "Singleton()" << endl;
}
~Singleton()
{
cout << "~Singleton()" << endl;
}
static Singleton * _pInstance;
};
Singleton * Singleton::_pInstance = getInstance();//饱汉式实现单例模式,可以保证线程安全
int main()
{
// Singleton single1;//这样写会报错:原因是构造和析构函数都放在了private内,但是,Singleton single1();这样写就可以,不知道为什么??
Singleton * pInst1 = Singleton::getInstance();
Singleton * pInst2 = Singleton::getInstance();
cout << "pInst1 = " << pInst1 << endl;
cout << "pInst2 = " << pInst2 << endl;
return 0;
}
饱汉式能够保证线程安全在上面就已经说了。这里只介绍atexit
为什么可以实现自动回收的功能。可以通过man atexit
查看到它的使用方式为:int atexit(void(*function)(void))
可知它有一个非常神奇的特性==atexit的参数为函数,改函数会在main函数执行结束以后被执行一次。==基于这个特性,我们可以将回收函数放到atexit
内部从而实现堆对象空间的回收。有关atexit
函数的内容可以参考Linux编程手册。
pthread_once+atexit实现单例模式
实例代码(代码10-4):
///
/// @file Singleton_pthread_once.cc
/// @author XuHuanhuan(1982299154@qq.com)
/// @date 2019-02-17 21:37:47
///
#include <pthread.h>
#include <stdlib.h>
#include <iostream>
using std::cout;
using std::endl;
class Singleton
{
public:
static Singleton * getInstance()
{
pthread_once(&one,init);
return _pInstance;
}
static void init()
{
_pInstance = new Singleton();
atexit(destory);
}
static void destory()
{
if(NULL != _pInstance)
{
delete _pInstance;
cout << "AutoRelease->delete success!" << endl;
}
}
private:
Singleton()
{
cout << "Singleton()" << endl;
}
~Singleton()
{
cout << "~Singleton()" << endl;
}
static Singleton * _pInstance;
static pthread_once_t one;
};
Singleton * Singleton::_pInstance = NULL;//懒汉式实现单例模式,不可以保证线程的安全
pthread_once_t Singleton::one = PTHREAD_ONCE_INIT;
int main()
{
// Singleton single1;//这样写会报错:原因是构造和析构函数都放在了private内,但是,Singleton single1();这样写就可以,不知道为什么??
Singleton * pInst1 = Singleton::getInstance();
Singleton * pInst2 = Singleton::getInstance();
cout << "pInst1 = " << pInst1 << endl;
cout << "pInst2 = " << pInst2 << endl;
return 0;
}
atexit
可以实现堆对象的自动回收这个在上面一节中讲过。这里就将pthread_once
的使用与功能。通过man pthread_once
参考Linux编程手册得知其使用方法如下:int pthread_once(pthread_once_t * once_control, void (*init_routine)(void)); pthread_once_t once_control = PTHREAD_ONCE_INIT;
man pthread_once
函数的功能实现的是其第二个参数为函数,该函数只能被线程执行一次。基于这个功能就可以实现单例模式只创建一个对象。