1.单例模式说明
在整个软件的运行过程中,让整个类有且只有一个实例化对象存在于整个进程中。
是最简单的一个设计模式,然后再项目开发中也是使用最广的。
2.使用单例模式的优点
1.节省资源:再整个软件的运行过程中,只有一个实例化对象,不用重新分配新的堆空间。
2.数据的传递:由于单例只会创建一个实例化对象,比如有一个在停车场对你的车辆进行计费的程序。但是计费需要多个步骤,这样每个步骤调用的都是同一个单例,就能够记录每个步骤计算后的结果,知道算出正确结果为止。
3.单例模式实现分析
1.构造函数只能调用一次
如果已经有了实例化对象,就直接返回生成的实例化对象
如果没有就调用一次构造函数
2.具体实现,让构造函数只能被调用一次
把构造函数写到private:下面
删除拷贝构造函数和赋值符号构造函数
3.对象的生成不能依赖对象,所以要设置成静态的。
4.返回的创建的对象不能是类类型,因为这样会生成零时变量,因此返回类型要用指针或者引用
4.实际的应用场景
1.项目中的日志模块,一个项目只有一个日志的实例化对象
2.项目中的进程监控模块。
5.最基础的懒汉模式
#include <iostream>
using namespace std;
class Singleton {
private:
static Singleton* m_pInstance;
private:
Singleton() {
cout << "constructor called!" << endl;
}
Singleton(Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
public:
~Singleton() {
cout << "destructor called!" << endl;
}
static Singleton* getInstance() {
if (m_pInstance == nullptr) {
m_pInstance = new Singleton;
}
return m_pInstance;
}
};
Singleton* Singleton::m_pInstance = nullptr;
int main()
{
Singleton* instance1 = Singleton::getInstance();
Singleton* instance2 = Singleton::getInstance();
std::cout << "Hello World!\n";
}
可以看到在main函数里面调用了两次getinstance,但是调用了一次构造函,说明达到了我们想要的效果
这个版本的懒汉模式还存在以下的缺陷
1.线程安全的问题:当多个线程同时获取单例的时候可能引发竞争的问题;当第一个线程
进入到 if (m_pInstance == nullptr),这个判断条件的时候,m_pInstance符合条件nullptr就会创建对应的实例化对象,与此同时,第二个线程也会进入到if (m_pInstance == nullptr)的判断。此时的m_pinstance也符合nullptr的条件,也会创建一个单例的实例化对象,此时就会有两个实例化对象,解决方法加锁
内存泄漏的问题:getInstance函数里面 ,new 了一个 类对象,但是没有进行释放,解决方法使用智能指针
6.线程安全,内存安全的懒汉模式(使用智能指针,加锁)
#include <iostream>
#include <mutex>
class Singleton {
public:
using Ptr = std::shared_ptr<Singleton>;
~Singleton() {
std::cout << "destructor called!" << std::endl;
}
Singleton(Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static Ptr getInstance() {
if (m_pInstance == nullptr) {
std::lock_guard<std::mutex> lk(m_mutex);
if (m_pInstance == nullptr) {
m_pInstance = std::shared_ptr<Singleton>(new Singleton);
}
}
return m_pInstance;
}
private:
Singleton() {
std::cout << "constructor called!" << std::endl;
}
private:
static Ptr m_pInstance;
static std::mutex m_mutex;
};
Singleton::Ptr Singleton::m_pInstance = nullptr;
std::mutex Singleton::m_mutex;
int main() {
Singleton::Ptr instance1 = Singleton::getInstance();
Singleton::Ptr instance2 = Singleton::getInstance();
return 0;
}
输出结果为
程序对堆空间进行了释放,解决了内存安全的问题,
又使用了锁,解决了线程安全的问题。
缺陷:单例使用了智能指针,要求调用的用户也需要使用智能指针,
使用了锁也会增加相应的开销。理论上肯定是希望我们设计的程序越简单越好
7.最推荐的懒汉式单例(magic static )——局部静态变量
#include <iostream>
#include <mutex>
class Singleton
{
public:
~Singleton() {
std::cout << "destructor called!" << std::endl;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static Singleton& getInstance() {
static Singleton instance;
return instance;
}
private:
Singleton() {
std::cout << "constructor called!" << std::endl;
}
};
int main(int argc, char* argv[])
{
Singleton& instance_1 = Singleton::getInstance();
Singleton& instance_2 = Singleton::getInstance();
return 0;
}
这种方法又叫做 Meyers' SingletonMeyer'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.
如果当变量在初始化的时候,并发同时进入声明语句,并发线程将会阻塞等待初始化结束。
这样保证了并发线程在获取静态局部变量的时候一定是初始化过的,所以具有线程安全性。
C++静态变量的生存期 是从声明到程序结束,这也是一种懒汉式。
8.单例模式之饿汉式
饿汉模式 的区别就是,在程序调用单例之前提前生成实例化对象。
这样就不存在多线程竞争的问题。
#include <iostream>
#include <mutex>
class Singleton {
public:
using Ptr = std::shared_ptr<Singleton>;
static Ptr getInstance() {
return m_pInstance;
}
~Singleton() {
std::cout << "destructor called!" << std::endl;
}
private:
static Ptr m_pInstance;
private:
Singleton() {
std::cout << "constructor called!" << std::endl;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
Singleton::Ptr Singleton::m_pInstance = std::shared_ptr<Singleton>(new Singleton);
int main(int argc, char* argv[])
{
Singleton::Ptr instance1 = Singleton::getInstance();
Singleton::Ptr instance2 = Singleton::getInstance();
return 0;
}
9.单例模式之单例模板
#include <iostream>
#include <mutex>
//单例模板
template <typename T>
class Singleton {
private:
Singleton() = default;
~Singleton() = default;
public:
Singleton(const Singleton&) = delete;
void operator = (const Singleton&) = delete;
static T* instance()
{
static T m_instance;
return &m_instance;
}
};
class Student {
public:
Student() {
std::cout << "constructor called!" << std::endl;
}
~Student() {
std::cout << "destructor called!" << std::endl;
}
private:
int num;
public:
void setnum(int value) {
num = value;
}
void getnum()
{
std::cout << num << std::endl;
}
};
int main(int argc, char* argv[])
{
Singleton<Student>::instance()->setnum(1);
Singleton<Student>::instance()->getnum();
Singleton<Student>::instance()->setnum(2);
Singleton<Student>::instance()->getnum();
}
输出结果
可以看到Student类调用了两次,但是只调用了一次构造函数,实现了我们想要的效果,其他的类也可以通过调用单列模板,达到这样类似的效果。
缺陷,不能阻止这样的声明出现 Student s; 在项目的其他地方声明这个类,也没问题。我们并没有禁止Student 类创建自己的对象
9.改善后的单例模板类
#include <iostream>
#include <mutex>
//单例模板
template <typename T>
class Singleton {
public:
Singleton() = default;
virtual ~Singleton() = default;
Singleton(const Singleton&) = delete;
void operator = (const Singleton&) = delete;
static T* instance()
{
static T m_instance;
return &m_instance;
}
};
class Student:public Singleton<Student> {
private:
Student() {
std::cout << "constructor called!" << std::endl;
}
~Student() {
std::cout << "destructor called!" << std::endl;
}
friend class Singleton<Student>;
private:
int num;
public:
void setnum(int value) {
num = value;
}
void getnum()
{
std::cout << num << std::endl;
}
};
int main(int argc, char* argv[])
{
//Student stu; 报错 不可访问构造函数
Student::instance()->setnum(1);
Student::instance()->getnum();
Student::instance()->setnum(2);
Student::instance()->getnum();
}
输出结果
本文只是做为一个总结,记录,实践作为自己学习用。参考了网上的很多其他文章。
主要参考了这篇C++ 单例模式-CSDN博客