单例模式(Singleton Pattern),使用最广泛的设计模式之一。其意图是保证一个类仅有一个实例被构造,并提供一个访问它的全局访问接口,该实例被程序的所有模块共享。
在程序启动后立刻构造单例,饿汉式实现一个单例类步骤如下:
代码实现
#include <iostream>
#include <string>
using namespace std;
// 单例类
class Singleton {
protected:
Singleton() { std::cout << "Singleton: call Constructor\n"; };
static Singleton *m_pInst;
public:
Singleton(const Singleton &) = delete;
Singleton &operator=(const Singleton &) = delete;
virtual ~Singleton() { std::cout << "Singleton: call Destructor\n"; }
static Singleton* GetInstance() {
return m_pInst;
}
};
Singleton *Singleton::m_pInst = new Singleton;
int main()
{
Singleton *pInst1 = Singleton::GetInstance();
Singleton *pInst2 = Singleton::GetInstance();
cout << "pInst1 : " << pInst1 << endl;
cout << "pInst2 : " << pInst2 << endl;
return 0;
}
输出结果
Singleton: call Constructor
pInst1 : 0xf71760
pInst2 : 0xf71760
Process returned 0 (0x0) execution time : 0.203 s
Press any key to continue.
从输出结果可以看出来,在执行
main
函数之前,单例类对象已经被创建出来。获取实例的函数也不需要进行判空操作,因此也就不用双重检测锁来保证线程安全了,它本身已经是线程安全状态了,但是内存泄漏的问题还是要解决的。
内存泄漏解决方法有两个:智能指针&静态嵌套类。
将实例指针更换为智能指针,另外智能指针在初始化时,还需要添加公有的销毁函数,因为析构函数私有化了。
#include <iostream>
#include <string>
#include <mutex>
#include <memory>
#include <thread>
using namespace std;
// 单例类
class Singleton {
protected:
Singleton() { std::cout << "Singleton: call Constructor\n"; };
static shared_ptr<Singleton> instance;
private:
Singleton(const Singleton &) = delete;
Singleton &operator=(const Singleton &) = delete;
virtual ~Singleton() { std::cout << "Singleton: call Destructor\n"; }
public:
// 自定义销毁实例方法
static void DestoryInstance(Singleton* x) {
delete x;
}
static shared_ptr<Singleton> GetInstance() {
return instance;
}
};
// 初始化
shared_ptr<Singleton> Singleton::instance(new Singleton(), DestoryInstance);
int main()
{
cout << "main开始" << endl;
thread t1([] {
shared_ptr<Singleton> s1 = Singleton::GetInstance();
});
thread t2([] {
shared_ptr<Singleton> s2 = Singleton::GetInstance();
});
t1.join();
t2.join();
cout << "main结束" << endl;
return 0;
}
输出结果
Singleton: call Constructor
main开始
main结束
Singleton: call Destructor
Process returned 0 (0x0) execution time : 0.116 s
Press any key to continue.
从输出结果可以看出来实例内存在程序运行结束后被正常释放。
类中定义一个嵌套类,初始化该类的静态对象,当程序结束时,该对象进行析构的同时,将单例实例也删除了。
#include <iostream>
#include <string>
#include <mutex>
#include <memory>
#include <thread>
using namespace std;
// 单例类
class Singleton {
// 定义一个删除器(嵌套类)
class Deleter {
public:
Deleter() {};
~Deleter() {
if (m_pInst != nullptr) {
cout << "删除器启动" << endl;
delete m_pInst;
m_pInst = nullptr;
}
}
};
protected:
Singleton() { std::cout << "Singleton: call Constructor\n"; };
static Deleter m_deleter;
static Singleton* m_pInst;
private:
Singleton(const Singleton &) = delete;
Singleton &operator=(const Singleton &) = delete;
virtual ~Singleton() { std::cout << "Singleton: call Destructor\n"; }
public:
static Singleton* GetInstance() {
return m_pInst;
}
};
Singleton *Singleton::m_pInst = new Singleton;
Singleton::Deleter Singleton::m_deleter;
int main()
{
cout << "main开始" << endl;
thread t1([] {
Singleton *pInst1 = Singleton::GetInstance();
});
thread t2([] {
Singleton *pInst2 = Singleton::GetInstance();
});
t1.join();
t2.join();
cout << "main结束" << endl;
return 0;
}
输出结果
Singleton: call Constructor
main开始
main结束
删除器启动
Singleton: call Destructor
Process returned 0 (0x0) execution time : 0.254 s
Press any key to continue.
从输出结果可以看出来单例类对象在程序运行结束时正常被释放。
在使用类对象(单例实例)时才会去创建,实现如下:
#include <iostream>
#include <string>
#include <mutex>
#include <memory>
#include <thread>
using namespace std;
// 单例类
class Singleton
{
public:
static Singleton* GetInstance() {
if (m_pInst == nullptr) {
m_pInst = new Singleton;
}
return m_pInst;
}
private:
// 私有构造函数
Singleton() { cout << "构造函数启动。" << endl; };
// 私有析构函数
~Singleton() { cout << "析构函数启动。" << endl; };
private:
static Singleton* m_pInst;
};
// 初始化
Singleton* Singleton::m_pInst = nullptr;
int main()
{
cout << "main开始" << endl;
thread t1([] {
Singleton *pInst1 = Singleton::GetInstance();
});
thread t2([] {
Singleton *pInst2 = Singleton::GetInstance();
});
t1.join();
t2.join();
cout << "main结束" << endl;
return 0;
}
上面的懒汉式存在两方面问题,一是:多线程场景存在并发问题;二是:创建的单例对象在使用完成后不会被释放存在资源泄露问题。
使用双重检查解决多线程并发问题,核心代码如下:
static Singleton* GetInstance() {
if (m_pInst == nullptr) {
// 双重检查
lock_guard<mutex> l(m_mutex);
if (m_pInst == nullptr) {
m_pInst = new Singleton();
}
}
return m_pInst;
}
双重检查能解决多线程并发问题,同时效率也比单检查要高,调用
GetInstance
时只有当单例对象没有被创建时才会加锁,下面是单检查的实现,通过对比即可发现双检查的优点,如下:
static Singleton* GetInstance() {
lock_guard<mutex> l(m_mutex);
if (m_pInst == nullptr) {
m_pInst = new Singleton();
}
return m_pInst;
}
C++11后,规定了局部静态对象在多线程场景下的初始化行为,只有在首次访问时才会创建实例,后续不再创建而是获取。若未创建成功,其他的线程在进行到这步时会自动等待。注意:C++11前的版本不是这样的。因为有上述的改动,所以出现了一种更简洁方便优雅的实现方法,基于局部静态对象实现,如下:
#include <iostream>
#include <string>
#include <mutex>
#include <memory>
#include <thread>
using namespace std;
// 单例类
class Singleton
{
public:
static Singleton* GetInstance() {
static Singleton instance;
return &instance;
}
private:
// 私有构造函数
Singleton() { cout << "构造函数启动。" << endl; };
// 私有析构函数
~Singleton() { cout << "析构函数启动。" << endl; };
};
int main()
{
cout << "main开始" << endl;
thread t1([] {
Singleton *pInst1 = Singleton::GetInstance();
});
thread t2([] {
Singleton *pInst2 = Singleton::GetInstance();
});
t1.join();
t2.join();
cout << "main结束" << endl;
return 0;
}