目录
前言:
在C++中有许多设计模式,单例模式就是其中的一种,该模式主要针对类而设计,确保在一个进程下该类只能实例化出一个对象,因此名为单例。而单例模式又分为饿汉方式和懒汉方式,饿汉方式指的是只要发出了对该类的需求,就会实例化对象。懒汉方式指的是即使发出了对该类的需求,但是不会立刻实例化对象,等到真正用到该对象时才会实例化对象。
示意图如下:
1、饿汉方式实现单例
饿汉方式表明只要提出需要,则立即用该类实例化对象,并且当前进程只能有一个该类型的对象。这个逻辑在代码中的体现是:只要我们提出需要某个类的实例化请求,则系统就会立即在物理内存中就会为该对象开辟空间,即使我们不对该对象做任何的使用,该对象也会静静的放在内存中。当我们真正要访问该对象时,只能通过唯一的途径访问。
代码实现饿汉方式:
cpp
#include <iostream>
#include <unistd.h>
using namespace std;
class Singleton
{
Singleton()//该类不能在外被构造
{
cout<<"程序运行时就打印该信息"<<endl;
}
~Singleton(){}//该类不能在外被析构
Singleton(const Singleton& )=delete;//该类不能在外被拷贝
Singleton& operator=(const Singleton& )=delete;//该类不能在外被赋值
static Singleton data;//该进程下唯一Singleton对象,即单例
public:
static Singleton *GetInstance()//外部只能通过调用该函数拿到data
{
return &data;
}
};
Singleton Singleton::data;//data的初始化
int main()
{
return 0;
}
运行结果:
从结果可以发现,代码中仅仅只是定义了一个类,还未手动对该类进行实例化,程序开始执行时系统就自动实例化了一个Singleton的data对象,因为Singleton类的构造函数被执行,说明构造了一个对象。
对上述代码的逻辑可以理解为:定义Singleton类就是提出需求,而程序一启动就执行构造函数说明提出需求后就立即得到了一个对象。
并且在该进程下只存在唯一的Singleton类型对象,并且访问该唯一对象的途径只能是调用静态成员函数GetInstance,并且当调用GetInstance静态函数说明"真正用到了该对象",测试代码如下:
cpp
#include <iostream>
#include <unistd.h>
using namespace std;
class Singleton
{
Singleton()//该类不能在外被构造
{
cout<<"程序运行时就打印该信息"<<endl;
}
~Singleton(){}//该类不能在外被析构
Singleton(const Singleton& )=delete;//该类不能在外被拷贝
Singleton& operator=(const Singleton& )=delete;//该类不能在外被赋值
static Singleton data;//该进程下唯一Singleton对象,即单例
public:
static Singleton *GetInstance()//外部只能通过调用该函数拿到data
{
return &data;
}
};
Singleton Singleton::data;//data的初始化
int main()
{
//以下两种实例化方式都无法实例化出对象
// Singleton s;
// Singleton s1(s);
cout<<Singleton::GetInstance()<<endl;
return 0;
}
运行结果:
2、懒汉方式实现单例
懒汉方式指的是当提出需求后,系统不会立即实例化出对象,而是先实例化一个指针,当真正需要用到该对象时,系统才会主动的去申请一个对象,并让该指针指向这个对象。
懒汉方式的代码如下:
cpp
#include <iostream>
#include <unistd.h>
using namespace std;
class Singleton // 懒汉
{
Singleton() // 该类不能在外被构造
{
cout << "程序运行时就打印该信息" << endl;
}
~Singleton() {} // 该类不能在外被析构
Singleton(const Singleton &) = delete; // 该类不能在外被拷贝
Singleton &operator=(const Singleton &) = delete; // 该类不能在外被赋值
static Singleton *data; // 使用Singleton*作为访问唯一对象的入口
static pthread_mutex_t lock_; // 保证线程安全
public:
static Singleton *GetInstance() // 外部只能通过调用该函数拿到data指针
{
if (data == nullptr)
{
pthread_mutex_lock(&lock_);
if (data == nullptr)
data = new Singleton();
pthread_mutex_unlock(&lock_);
}
return data;
}
};
Singleton* Singleton::data = nullptr; // data的初始化
pthread_mutex_t Singleton::lock_ = PTHREAD_MUTEX_INITIALIZER;//锁的初始化
int main()
{
return 0;
}
运行结果:
可以发现,当程序一启动时,并没有直接构造一个Singleton类型的对象,因为没有调用Singleton的构造函数,只是简单的将data指针初始化为nullptr,只有当调用静态函数GetInstance时,系统才会实例化出对象,因为调用GetInstance表示要用到该对象,这时候就可以实例化对象了。
调用函数GetInstance的代码如下:
cpp
#include <iostream>
#include <unistd.h>
using namespace std;
class Singleton // 懒汉
{
Singleton() // 该类不能在外被构造
{
cout << "程序运行时就打印该信息" << endl;
}
~Singleton() {} // 该类不能在外被析构
Singleton(const Singleton &) = delete; // 该类不能在外被拷贝
Singleton &operator=(const Singleton &) = delete; // 该类不能在外被赋值
static Singleton *data; // 使用Singleton*作为访问唯一对象的入口
static pthread_mutex_t lock_; // 保证线程安全
public:
static Singleton *GetInstance() // 外部只能通过调用该函数拿到data指针
{
if (data == nullptr)
{
pthread_mutex_lock(&lock_);
if (data == nullptr)
data = new Singleton();
pthread_mutex_unlock(&lock_);
}
return data;
}
};
Singleton* Singleton::data = nullptr; // data的初始化
pthread_mutex_t Singleton::lock_ = PTHREAD_MUTEX_INITIALIZER;//锁的初始化
int main()
{
//只有调用GetInstance时,才会开辟空间
cout << Singleton::GetInstance() << endl;
return 0;
}
运行结果:
从结果可以看到,只要真正要用到该对象,系统才会实例化该对象,没有用到该对象前,系统只有一个指针做"准备就绪"的工作。
3、单例模式的总结
不管是饿汉方式还是懒汉方式,基本实现都是依靠static修饰的成员变量,并且该静态变量要放在类内,因为单例模式下构造函数是在私有域中的, 静态变量只有在类内才可以调用该类的构造函数进行初始化,这也从侧面表示出静态成员变量的类型必须和当前类的类型是一样的。拿到该类的实例化对象的途径只有通过调用该类的静态成员函数去访问。
一般懒汉方式用的最多,因为懒汉在局部上加快了速度,因为他改变的是花费时间的结构,比如要加载某个任务,若把整个任务都加载下来则需要很多时间,但是我们可以先加载任务的一小部分先用上,而无需等待整个任务都加载下来,等到真正使用该任务的时候在进行下载,可以将空间利用率最大化。
结语
以上就是关于单例模式的讲解,单例模式下用的最多的是懒汉方式,因为他可以将内存的空间利用率最大化,无论是饿汉方式还是懒汉方式,本质上是利用了static静态变量在进程的唯一性。
最后如果本文有遗漏或者有误的地方欢迎大家在评论区补充,谢谢大家!!