单例模式(重点*)
单例模式是23种常用设计模式中最简单的设计模式之一,它提供了一种创建对象的方式,确保只有单个对象被创建。这个设计模式主要目的是想在整个系统中只能出现类的一个实例,即一个类只有一个对象。
将单例对象创建在静态区
根据已经学过的知识进行分析:
-
将构造函数私有;
-
通过静态成员函数getInstance创建局部静态对象,确保对象的生命周期和唯一性;
-
getInstance的返回值设为引用,避免复制;
隐患:如果单例对象所占空间较大,可能会对静态区造成内存压力。
class Point
{
public:
//定义为静态函数是因为要创建对象而调用成员函数又需要对象来调用,所以就将成员函数定义为静态成员函数直接使用类来进项调用
// 使用& 是因为防止返回发生拷贝构造
static Point & getInstance(){
static Point pt(1,2);
return pt;
}
void print() const{
cout << "(" << this->_ix
<< "," << this->_iy
<< ")" << endl;
}
private:
Point(int x,int y)
: _ix(x)
, _iy(y)
{
cout << "Point(int,int)" << endl;
}
private:
int _ix;
int _iy;
};
void test0(){
//使用&来接收就不会发生拷贝构造
//这是pt和pt2指向就是相同的
Point & pt = Point::getInstance();
pt.print();
Point & pt2 = Point::getInstance();
pt2.print();
cout << &pt << endl;
cout << &pt2 << endl;
}
将单例对象创建在堆区
既然将单例对象创建在全局/静态区可能会有内存压力,那么为这个单例对象动态分配空间是比较合理的选择。请尝试实现代码:
分析:
-
构造函数私有;
-
因为非静态对象没有唯一性,所以我们要人为加一个判断语句来看是否第一次调用getInstance函数,所以在类中声明一个静态成员(因为我们想用它在静态成员函数中做判断)来接收通过静态成员函数getInstance创建堆上的对象,返回Point*类型的指针,如果该静态成员_pInstance为空就可以创建对象;
-
通过静态成员函数完成堆对象的回收。
多个指针指向同一块空间,是比较危险的,如果我像上面代码那样将代码进行回收了,我在用其他指向原来那块空间的指针来访问就会出问题,所以为了避免问题,就使用下面的单例模式的规范。
可能会对析构函数产生误解,析构函数是用来回收数据成员申请的堆空间的,而上面的数据成员并没有申请堆空间。
假如是将代码加到析构函数中使用析构函数来回收空间会怎么样呢?
调用析构函数,进入if判断不为nullptr,调用delete,而delete的第一步又是调用析构函数,这样就进去无限的循环直到栈的空间被占满。
这里不使用注释那样来调用destory是因为destory一开始设计不是static,需要对象来调用,而且这也还会再一次调用创建对象的成员函数,所以直接将destory设计为static,直接使用类名来调用。
上面定义的是一个死的数据因为是单例模式只能让他进行一次初始化,我们如果想要使单例的数据可以修改呢?
我们可以将上面的getInstance的构造函数改为无参构造只进行空间的申请,不对空间的数据进行初始化,然后通过这个函数的返回值再次调用初始化数据函数(init),使数据可以进行修改。
单例对象的数据成员申请堆空间
要求:实现一个单例的Computer类,包含品牌和价格信息。
#include <string.h>
#include <iostream>
using std ::cout;
using std ::endl;
class Computer
{
public:
static Computer *getInstance()
{
if (_pInstance == nullptr)
_pInstance = new Computer();
return _pInstance;
}
static void destroy()
{
if (_pInstance)
{
delete _pInstance;
_pInstance = nullptr;
}
cout << "heap delete" << endl;
}
void init(const char *brand, double price)
{
if (_brand)
{
delete[] _brand;
_brand = nullptr;
}
_brand = new char[strlen(brand) + 1]();
strcpy(_brand, brand);
_price = price;
}
void print()
{
cout << _brand << endl;
cout << _price << endl;
}
private:
// 构造函数
Computer() {};
Computer(const char *brand, double price)
: _brand(new char[strlen(brand) + 1]()), _price(price)
{
strcpy(_brand, brand);
}
// 析构函数
~Computer()
{
if (_brand)
{
delete _brand;
_brand = nullptr;
}
cout << "~Computer" << endl;
}
Computer(const Computer &rhs) = delete;
Computer &operator=(const Computer &rhs) = delete;
char *_brand;
double _price;
static Computer *_pInstance;
};
Computer *Computer ::_pInstance = nullptr;
int main()
{
Computer ::getInstance()->init("bob", 2222);
Computer ::getInstance()->print();
Computer ::getInstance()->init("tom", 6666);
Computer ::getInstance()->print();
Computer ::destroy();
return 0;
}
单例模式的应用场景
1、有频繁实例化然后销毁的情况,也就是频繁的 new 对象,可以考虑单例模式;
2、创建对象时耗时过多或者耗资源过多,但又经常用到的对象;
3、当某个资源需要在整个程序中只有一个实例时,可以使用单例模式进行管理(全局资源管理)。例如数据库连接池、日志记录器等;
4、当需要读取和管理程序配置文件时,可以使用单例模式确保只有一个实例来管理配置文件的读取和写入操作(配置文件管理);
5、在多线程编程中,线程池是一种常见的设计模式。使用单例模式可以确保只有一个线程池实例,方便管理和控制线程的创建和销毁;
6、GUI应用程序中的全局状态管理:在GUI应用程序中,可能需要管理一些全局状态,例如用户信息、应用程序配置等。使用单例模式可以确保全局状态的唯一性和一致性。