一、背景
单例模式在c++项目中被广泛的使用,主要的场景大概是有一个需要频繁被多个其他对象调用的类,设置成单例模式使调用者更简单高效。刚好今天在一个项目中需要使用这种模式来开发业务。特此记录一下。废话不多说,直接进入主题,分享一下几种单例模式的实现方式
二、为什么要用单例模式
首先我们得先弄清楚为什么需要单例模式。其实如果只是简单的想调用其他类的成员变量,定义一个类成员即可通讯。但是如果出现这样一种情况,当这个类是需要频繁被外部调用的时候,如果每次都创建一个类,那势必会造成内存的飙升,系统的性能就会下降。所以单例模式就出现了。单例顾名思义就是一直只会有一个实例对象,这样子不管被调用多少次,都只会占用一次内存。其实所谓的单例就是创建一个全局的的访问点,就能很好的规避需要频繁创建对象的问题。
三、单例模式的特点
1、私有化构造函数,包括拷贝构造,赋值操作符等都需要私有化。这样外界就无法自由的new对象了,进而成功的阻止了多个实例的产生。
2、类定义中含有该类唯一静态私有对象:静态变量是存储在数据段中的,是唯一的且能让所有对象都访问得到
3、提供一个共有的静态函数来获取实例
四、懒汉模式的实现
这种模式简单来说就是在使用对象的时候才回去创建它,不然就懒得去new,俗称懒汉式,
4-1:基础版本的实现
下面就先实现一个简单的实例来直观感受一下,所有的代码可以直接再在线平台 GDB Online调试运行。代码和运行结果都贴在下面。
/******************************************************************************
Welcome to GDB Online.
GDB online is an online compiler and debugger tool for C, C++, Python, Java, PHP, Ruby, Perl,
C#, OCaml, VB, Swift, Pascal, Fortran, Haskell, Objective-C, Assembly, HTML, CSS, JS, SQLite, Prolog.
Code, Compile, Run and Debug online from anywhere in world.
*******************************************************************************/
#include <iostream>
using namespace std;
//声明一个单例类
class Singleton
{
private:
//将构造函数,拷贝构造,赋值运算符都定义为私有,不允许外部类操作
Singleton(void){
cout<<"构造函数Singleton()"<<endl;
}; //构造函数
Singleton(const Singleton & sig); //拷贝构造函数
~Singleton();
Singleton & operator=(const Singleton & sig); //赋值运算符重载
private:
static Singleton * ins; //私有静态对象
public:
static Singleton * getInstance(){ //获取实例对象的静态共有函数,可以传参的
if(!ins){
ins = new Singleton();
}
return ins;
};
public:
void testPrint(){
cout<<"This is Sigleton test info"<<endl;
};
};
Singleton * Singleton::ins = nullptr;
int main()
{
Singleton * cSigleton_1 = Singleton::getInstance();
cSigleton_1->testPrint();
Singleton * cSigleton_2 = Singleton::getInstance();
cSigleton_2->testPrint();
Singleton * cSigleton_3 = Singleton::getInstance();
cSigleton_3->testPrint();
cout<<"main end"<<endl;
return 0;
}
从结果来看已经实现了单例模式,因为获取了三次实例,只进了一次构造函数,创建了一个实例。但是这个程序至少还存在着两个问题。1,存在线程安全问题; 2,有内存泄漏问题,new出来的空间没有delete。接下来我们逐步优化这两个问题。
4-2:线程安全问题优化
如果有多个线程同时获取实例,可能会出现new多次实例对象的问题,因为ins在多个线程之间可能同时是空的,这时候这些线程就都会new一个实例出来。要解决这个问题就必须加锁来实现互斥操作,保证只有一个线程去new 实例。改善代码如下所示:
/******************************************************************************
Welcome to GDB Online.
GDB online is an online compiler and debugger tool for C, C++, Python, Java, PHP, Ruby, Perl,
C#, OCaml, VB, Swift, Pascal, Fortran, Haskell, Objective-C, Assembly, HTML, CSS, JS, SQLite, Prolog.
Code, Compile, Run and Debug online from anywhere in world.
*******************************************************************************/
#include <iostream>
#include <mutex>
#include <thread>
using namespace std;
//声明一个单例类
class Singleton
{
private:
//将构造函数,拷贝构造,赋值运算符都定义为私有,不允许外部类操作
Singleton(void){
cout<<"构造函数Singleton()"<<endl;
}; //构造函数
Singleton(const Singleton & sig); //拷贝构造函数
~Singleton();
Singleton & operator=(const Singleton & sig); //赋值运算符重载
private:
static Singleton * ins; //私有静态对象
static mutex m_mutex;
public:
static Singleton * getInstance(){ //获取实例对象的静态共有函数,可以传参的
lock_guard<mutex> l(m_mutex); //必须加锁
if(!ins){
ins = new Singleton();
}
return ins;
};
public:
void testPrint(){
cout<<"This is Sigleton test info"<<endl;
};
};
Singleton * Singleton::ins = nullptr;
mutex Singleton::m_mutex;
int main()
{
thread t1([] {
Singleton* cSingleton_1 = Singleton::getInstance();
cout << "&cSingleton_1:" << cSingleton_1 << endl;
});
thread t2([] {
Singleton* cSingleton_2 = Singleton::getInstance();
cout << "&cSingleton_2:" << cSingleton_2<< endl;
});
t1.join();
t2.join();
Singleton* cSingleton_3 = Singleton::getInstance();
cout << "&cSingleton_3:" << cSingleton_3<< endl;
cSingleton_3->testPrint();
cout<<"main end"<<endl;
return 0;
}
从结果中可以看到,这个三个实例地址都是同一个,说明多线程获取单例类也只是创建了一个实例。现在我们将加锁的那行代码屏蔽运行一下看看结果,如下图所示:
从上图结果可知,不加锁,就会创建两个实例,每个线程都去创建的实例。仅仅只是把加锁的那行代码屏蔽了。所以可以通过加锁的方式来解决线程不安全的问题
4-3:内存泄漏问题优化
单例类中new出来的实例没有得到释放,所以需要方法来delete这个对象。解决办法有两个:(1):通过智能指针 (2) 通过嵌套类
1:智能指针方式
通过将实例变量声明为智能共享指针的方式,在初始化智能指针的时候需要认为的添加公有销毁函数,因为析构函数私有化了,无法在外部调用。
/******************************************************************************
Welcome to GDB Online.
GDB online is an online compiler and debugger tool for C, C++, Python, Java, PHP, Ruby, Perl,
C#, OCaml, VB, Swift, Pascal, Fortran, Haskell, Objective-C, Assembly, HTML, CSS, JS, SQLite, Prolog.
Code, Compile, Run and Debug online from anywhere in world.
*******************************************************************************/
#include <iostream>
#include <mutex>
#include <thread>
#include <boost/make_shared.hpp>
using namespace std;
//声明一个单例类
class Singleton
{
private:
//将构造函数,拷贝构造,赋值运算符都定义为私有,不允许外部类操作
Singleton(void){
cout<<"构造函数Singleton()"<<endl;
};
Singleton(const Singleton & sig); //拷贝构造函数
~Singleton(){
cout<<"析构函数~Singleton()"<<endl;
};
Singleton & operator=(const Singleton & sig); //赋值运算符重载
private:
static boost::shared_ptr<Singleton> ins;
static mutex m_mutex;
public:
static boost::shared_ptr<Singleton> getInstance(){ //获取实例对象的静态共有函数,可以传参的
if(!ins){ //不需要每次都进来获取锁资源,提高效率
lock_guard<mutex> l(m_mutex); //必须加锁
if(!ins){
ins.reset(new Singleton(), destoryInstance);
}
}
return ins;
};
static void destoryInstance(Singleton * instance){
cout<<"销毁单例对象资源(delete ins)"<<endl;
delete instance;
};
public:
void testPrint(){
cout<<"This is Sigleton addr"<< ins << endl;
};
};
boost::shared_ptr<Singleton> Singleton::ins = nullptr;
mutex Singleton::m_mutex;
int main()
{
thread t1([] {
boost::shared_ptr<Singleton> cSingleton_1 = Singleton::getInstance();
cout << "&cSingleton_1:" << cSingleton_1 << endl;
});
thread t2([] {
boost::shared_ptr<Singleton> cSingleton_2 = Singleton::getInstance();
cout << "&cSingleton_2:" << cSingleton_2<< endl;
});
t1.join();
t2.join();
boost::shared_ptr<Singleton> cSingleton_3 = Singleton::getInstance();
cout << "&cSingleton_3:" << cSingleton_3<< endl;
cSingleton_3->testPrint();
cout<<"main end"<<endl;
return 0;
}
通过上述日志可以看出,单例对象已经被销毁回收了。智能共享指针需要用到boost库,添加相应的hpp文件 boost/make_shared.hpp
2,通过内嵌类的方式释放资源
在单例类中内嵌一个类,并初始化一个静态对象。当程序结束的时候,该对象进到析构的同时,将单例实例删除。这样就能回收单例实例的资源。实现如下
/******************************************************************************
Welcome to GDB Online.
GDB online is an online compiler and debugger tool for C, C++, Python, Java, PHP, Ruby, Perl,
C#, OCaml, VB, Swift, Pascal, Fortran, Haskell, Objective-C, Assembly, HTML, CSS, JS, SQLite, Prolog.
Code, Compile, Run and Debug online from anywhere in world.
*******************************************************************************/
#include <iostream>
#include <mutex>
#include <thread>
#include <boost/make_shared.hpp>
using namespace std;
//声明一个单例类
class Singleton
{
private:
//将构造函数,拷贝构造,赋值运算符都定义为私有,不允许外部类操作
Singleton(void){
cout<<"构造函数Singleton()"<<endl;
};
Singleton(const Singleton & sig); //拷贝构造函数
~Singleton(){
cout<<"析构函数~Singleton()"<<endl;
};
Singleton & operator=(const Singleton & sig); //赋值运算符重载
private:
class Deleter{
public:
Deleter(){};
~Deleter(){
if(ins != nullptr){
cout << "释放单例实例资源"<<endl;
delete ins;
ins = nullptr;
}
};
};
static Deleter m_deleter;
static Singleton * ins;
static mutex m_mutex;
public:
static Singleton * getInstance(){ //获取实例对象的静态共有函数,可以传参的
if(!ins){ //不需要每次都进来获取锁资源,提高效率
lock_guard<mutex> l(m_mutex); //必须加锁
if(!ins){
ins = new Singleton();
}
}
return ins;
};
public:
void testPrint(){
cout<<"This is Sigleton addr"<< ins << endl;
};
};
Singleton::Deleter Singleton::m_deleter;
Singleton * Singleton::ins = nullptr;
mutex Singleton::m_mutex;
int main()
{
thread t1([] {
Singleton * cSingleton_1 = Singleton::getInstance();
cout << "&cSingleton_1:" << cSingleton_1 << endl;
});
thread t2([] {
Singleton * cSingleton_2 = Singleton::getInstance();
cout << "&cSingleton_2:" << cSingleton_2<< endl;
});
t1.join();
t2.join();
Singleton * cSingleton_3 = Singleton::getInstance();
cout << "&cSingleton_3:" << cSingleton_3<< endl;
cSingleton_3->testPrint();
cout<<"main end"<<endl;
return 0;
}
从上述打印信息可以看到,单例实例的资源已经被释放,这样就没有内存泄露了。
4-4: 基于静态局部变量的实现方式
C++11标准规定局部的静态对象在多线程场景下,只有初次访问才会被创建实例,后续都是直接获取。若未创建成功,其他线程就会等待,不会出现竞争的情况。而且资源会自动被销毁释放。这种方式是最便捷也是最被推荐的一种实现单例模式的方式。但是请注意,C++11以前的标准并不支持这项功能。实现代码和调试结果如下:
/******************************************************************************
Welcome to GDB Online.
GDB online is an online compiler and debugger tool for C, C++, Python, Java, PHP, Ruby, Perl,
C#, OCaml, VB, Swift, Pascal, Fortran, Haskell, Objective-C, Assembly, HTML, CSS, JS, SQLite, Prolog.
Code, Compile, Run and Debug online from anywhere in world.
*******************************************************************************/
#include <iostream>
#include <mutex>
#include <thread>
#include <boost/make_shared.hpp>
using namespace std;
//声明一个单例类
class Singleton
{
private:
//将构造函数,拷贝构造,赋值运算符都定义为私有,不允许外部类操作
Singleton(void){
cout<<"构造函数Singleton()"<<endl;
};
Singleton(const Singleton & sig); //拷贝构造函数
~Singleton(){
cout<<"析构函数~Singleton()"<<endl;
};
Singleton & operator=(const Singleton & sig); //赋值运算符重载
public:
static Singleton & getInstance(){ //获取实例对象的静态共有函数,可以传参的
static Singleton ins;
cout << "单例实例的地址:"<<&ins << endl;
return ins;
};
public:
void testPrint(){
cout<<"This is Sigleton test" << endl;
};
};
int main()
{
thread t1([] {
Singleton& cSingleton_1 = Singleton::getInstance();
});
thread t2([] {
Singleton& cSingleton_2 = Singleton::getInstance();
});
t1.join();
t2.join();
Singleton& cSingleton_3 = Singleton::getInstance();
cSingleton_3.testPrint();
cout<<"main end"<<endl;
return 0;
}
可以看到,每次获取的实例都是同一个地址,而且是通过静态局部变量获取,程序退出的时候系统会自动回收其资源。
五、饿汉模式的实现:
单例实例在类装载的时候就已构建,静态编译的时候就已经分配好了空间,等到需要用时直接通过静态共有成员接口获取到这个实例即可,不需要临时再分配。因为这种模式一开始就创建,感觉就很急促很饿一样,所以就俗称饿汉式。饿汉模式的实现和懒汉模式相比就是new出来一个全局的类对象。这种方式本身就已经是线程安全了,所以不需要加锁。只需要将申请的资源释放,这里就采用共享指针的方式来实现。代码如下:
/******************************************************************************
Welcome to GDB Online.
GDB online is an online compiler and debugger tool for C, C++, Python, Java, PHP, Ruby, Perl,
C#, OCaml, VB, Swift, Pascal, Fortran, Haskell, Objective-C, Assembly, HTML, CSS, JS, SQLite, Prolog.
Code, Compile, Run and Debug online from anywhere in world.
*******************************************************************************/
#include <iostream>
#include <mutex>
#include <thread>
#include <boost/make_shared.hpp>
using namespace std;
//声明一个单例类
class Singleton
{
private:
//将构造函数,拷贝构造,赋值运算符都定义为私有,不允许外部类操作
Singleton(void){
cout<<"构造函数Singleton()"<<endl;
};
Singleton(const Singleton & sig); //拷贝构造函数
~Singleton(){
cout<<"析构函数~Singleton()"<<endl;
};
Singleton & operator=(const Singleton & sig); //赋值运算符重载
public:
static boost::shared_ptr<Singleton> getInstance(){ //获取实例对象的静态共有函数,可以传参的
return ins;
};
static void destoryInstance(Singleton * instance){
cout<<"销毁单例对象资源(delete ins)"<< instance <<endl;
delete instance;
};
private:
static boost::shared_ptr<Singleton> ins;
public:
void testPrint(){
cout<<"This is Sigleton addr:"<< ins << endl;
};
};
boost::shared_ptr<Singleton> Singleton::ins(new Singleton(), destoryInstance);
int main()
{
thread t1([] {
boost::shared_ptr<Singleton> cSingleton_1 = Singleton::getInstance();
cout << "&cSingleton_1:" << cSingleton_1 << endl;
});
thread t2([] {
boost::shared_ptr<Singleton> cSingleton_2 = Singleton::getInstance();
cout << "&cSingleton_2:" << cSingleton_2<< endl;
});
t1.join();
t2.join();
boost::shared_ptr<Singleton> cSingleton_3 = Singleton::getInstance();
cout << "&cSingleton_3:" << cSingleton_3<< endl;
cSingleton_3->testPrint();
cout<<"main end"<<endl;
return 0;
}