只能生成一个实例的类是实现了单例模式的类。
singleton是唯一一个能够用短短几十行代码完整实现的模式。
懒汉模式:
故名思义,不到万不得已就不会去实例化类,也就是说在第一次用到类实例的时候才会去实例化。
懒汉式是典型的时间换空间,也就是每次获取实例都会进行判断,看是否需要创建实例,浪费判断的时间。当然,如果一直没有人使用的话,那就不会创建实例,则节约内存空间。调用效率低,可以延时加载。
一、懒汉模式(有缺陷):
#include "../common.h"
//final表示该类不可被继承
class CMySingleton final
{
public:
~CMySingleton();
static CMySingleton* getInstance(char *pData);
private:
CMySingleton(char *pData);
char *m_data;
static CMySingleton *pInstance;
};
CMySingleton::CMySingleton(char *pData)
{
printf("This is Constructors (data:%s) ...\n",pData);
}
CMySingleton::~CMySingleton()
{
printf("This is Destructor ...\n");
if(m_data != nullptr)
{
delete []m_data;
m_data = nullptr;
}
/*绝对不能在析构函数里面释放类对象,不然会一直递归运行!!!
if(pInstance != nullptr)
{
delete pInstance;
pInstance = nullptr;
}*/
}
CMySingleton *CMySingleton::getInstance(char *pData)
{
if(pInstance == nullptr)
{
pInstance = new CMySingleton(pData);
}
return pInstance;
}
CMySingleton *CMySingleton::pInstance = nullptr;
void *pthCreate(void*)
{
printf("into pthCreate() ... \n");
CMySingleton *singleton_1 = CMySingleton :: getInstance(nullptr);
}
int main()
{
char data[] = "abcdefg";
printf("----------------1-----------------\n");
{
CMySingleton *singleton_1 = CMySingleton :: getInstance(data);
CMySingleton *singleton_2 = CMySingleton :: getInstance(data);
delete singleton_1; //如果不确定是否会再次使用单例类,就不要delete,不会自动析构
//delete singleton_2; //delete除了执行析构函数,还会释放掉对象本身的空间,多次释放会导致double free。
}
/* printf("-----------------2----------------\n");
{
pthread_t pid1 = 0, pid2 = 0;
pthread_create(&pid1,NULL,pthCreate,NULL);
pthread_create(&pid2,NULL,pthCreate,NULL);
sleep(100); //防止线程还未创建进程就结束
}*/
}
使用第一段代码时,结果:
----------------1-----------------
This is Constructors (data:abcdefg) ...
This is Destructor ...
可以看出只实例化了一个对象。
使用第二段代码时,结果:
-----------------2----------------
into pthCreate() ...
into pthCreate() ...
This is Constructors (data:(null)) ...
This is Constructors (data:(null)) ...
可以看到构造函数运行了两次,这是因为多线程有可能引发竞态条件。
上边的类中只负责new对象,却没有delete对象,会不会有内存泄漏?
答:一般情况下,单例类的实例都是常驻内存的,一直存在于进程的生命周期,因此不需要手动释放。如果的确需要释放实例占用的内存,一定不能在单例类的析构函数中进行delete操作,这样会造成无限循环。
为了解决上面两个问题,我们可以修改程序:
二、懒汉模式(无缺陷):
#include "../common.h"
#include <mutex>
//final表示该类不可被继承
class CMySingleton final
{
public:
typedef std::shared_ptr<CMySingleton> CMySingletonPtr; //智能指针
~CMySingleton();
static CMySingletonPtr getInstance(char *pData);
CMySingleton(CMySingleton&)=delete;
CMySingleton& operator=(const CMySingleton&)=delete;
private:
CMySingleton(char *pData);
char *m_data;
static CMySingletonPtr pInstance;
static std::mutex tMutex;
};
CMySingleton::CMySingleton(char *pData)
{
printf("This is Constructors (data:%s) ...\n",pData);
}
CMySingleton::~CMySingleton()
{
printf("This is Destructor ...\n");
if(m_data != nullptr)
{
delete []m_data;
m_data = nullptr;
}
}
CMySingleton::CMySingletonPtr CMySingleton::getInstance(char *pData)
{
lock_guard<mutex> lock(tMutex);
if(pInstance == nullptr)
{
pInstance = CMySingletonPtr(new CMySingleton(pData));
}
return pInstance;
}
CMySingleton::CMySingletonPtr CMySingleton::pInstance = nullptr;
std::mutex CMySingleton::tMutex;
void *pthCreate(void*)
{
printf("into pthCreate() ... \n");
CMySingleton::CMySingletonPtr singleton_1 = CMySingleton :: getInstance(nullptr);
}
int main()
{
char data[] = "abcdefg";
/*printf("----------------1-----------------\n");
{
CMySingleton::CMySingletonPtr singleton_1 = CMySingleton :: getInstance(data);
CMySingleton::CMySingletonPtr singleton_2 = CMySingleton :: getInstance(data);
}*/
printf("-----------------2----------------\n");
{
pthread_t pid1 = 0, pid2 = 0;
pthread_create(&pid1,NULL,pthCreate,NULL);
pthread_create(&pid2,NULL,pthCreate,NULL);
sleep(100); //防止线程还未创建进程就结束
}
}
通过加锁解决了多线程问题,运行第二段代码段结果:
-----------------2----------------
into pthCreate() ...
into pthCreate() ...
This is Constructors (data:(null)) ...
可以看到只运行了一次构造函数
接下来运行第一段代码:
----------------1-----------------
This is Constructors (data:abcdefg) ...
This is Destructor ...
可以看到程序结束前自动调用析构函数。
三、懒汉模式——局部静态变量
#include "../common.h"
//final表示该类不可被继承
class CMySingleton final
{
public:
~CMySingleton();
static CMySingleton &getInstance(char *pData);
CMySingleton(CMySingleton &) = delete;
CMySingleton &operator=(const CMySingleton &) = delete;
private:
CMySingleton(char *pData);
char *m_data;
};
CMySingleton::CMySingleton(char *pData)
{
printf("This is Constructors (data:%s) ...\n", pData);
}
CMySingleton::~CMySingleton()
{
printf("This is Destructor ...\n");
if (m_data != nullptr)
{
delete[] m_data;
m_data = nullptr;
}
}
CMySingleton &CMySingleton::getInstance(char *pData)
{
//lock()如果编译器不支持,还是只能加锁解锁
static CMySingleton pInstance(pData);
return pInstance;
}
void *pthCreate(void *)
{
printf("into pthCreate() ... \n");
CMySingleton &singleton_1 = CMySingleton ::getInstance(nullptr);
}
int main()
{
char data[] = "abcdefg";
/*printf("----------------1-----------------\n");
{
CMySingleton &singleton_1 = CMySingleton :: getInstance(data);
CMySingleton &singleton_2 = CMySingleton :: getInstance(data);
}*/
printf("-----------------2----------------\n");
{
pthread_t pid1 = 0, pid2 = 0;
pthread_create(&pid1, NULL, pthCreate, NULL);
pthread_create(&pid2, NULL, pthCreate, NULL);
sleep(100); //防止线程还未创建进程就结束
}
}
结果:
----------------1-----------------
This is Constructors (data:abcdefg) ...
This is Destructor ...
-----------------2----------------
into pthCreate() ...
This is Constructors (data:(null)) ...
into pthCreate() ...
它的效果与无缺陷版是一样的,但是代码更加简洁、简单。
这种方法又叫做 Meyers’ Singleton
Meyer’s的单例
, 是著名的写出《Effective C++》系列书籍的作者 Meyers 提出的。所用到的特性是在C++11标准中的
Magic Static
特性:
If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.
如果当变量在初始化的时候,并发同时进入声明语句,并发线程将会阻塞等待初始化结束。
这样保证了并发线程在获取静态局部变量的时候一定是初始化过的,所以具有线程安全性。