单例模式只允许创建一个对象,因此节省内存,加快对象访问速度,因此对象需要被公用的场合适合使用。
特点:
- 程序运行中一个类始终只能创建一个对象
- 构造方法私有化
- 调用静态成员方法获取对象
使用场景:
Windows的Task Manager(任务管理器)、回收站:
无论打开几次任务管理器都只有一个程序。所有删除的文件都只丢进一个垃圾桶。没有多个实例的必要。应用程序的日志应用:
一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作。线程池:
使用线程池来管理线程的好处是线程池中的线程可以复用,在一个线程使用过时候,再返回到线程池中,不需要每次都创建一个线程。由于线程池是公共的,因此我们使用单例模式来保证线程池有且仅有一个。应用程序的日志应用:
如果不用单例的话,每次都要 new 对象,每次都要重新读一遍配置文件,很影响性能网站在线人数统计:
其实就是全局计数器,也就是说所有用户在相同的时刻获取到的在线人数数量都是一致的。要实现这个需求,计数器就要全局唯一,也就正好可以用单例模式来实现。当然这里不包括分布式场景,因为计数是存在内存中的,并且还要保证线程安全。
单例模式应用的场景一般存在以下条件:
- 资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如上述中的日志文件,应用配置。
- 控制资源的情况下,方便资源之间的互相通信。如线程池等。
实现方法分析:
- 由于构造方法是私有的所以在类外无法调用构造函数,故需在类中实现一个函数创建对象并且返回
- 由于成员函数需要由实例对象去调用,静态成员函数则可以用类名+类作用域符去调用。故该函数应该是静态成员函数。
- 由于没有拷贝构造函数。故函数返回值应该是对类的引用或者指针类型。
- 由于是用函数返回对象。防止在栈上初始化对象导致自动释放内存。故应该用指针(指针法:需要手动释放对象),或者用static(静态局部变量法:不需要手动释放对象)去创建对象。
- 由于该函数的每次调用必须保证返回值都是同一块内存,故如果用指针法需要将对象指针定义为类的静态成员,每次调用时判断是否为空再决定是否去分配内存。由于static只初始化一次,故不需要判断。
- 对象的释放。
指针方法:如果在析构函数中delete对象指针将再次调用析构函数 一直处于死循环。故我们需要提供相应的接口方法去释放。因为可能有多个指针变量指向这段内存,固需要用引用计数取记录指针的数量。当指向这段内存的指针数量为0时,释放这段内存。
静态局部变量法:不需要手动释放变量
代码实现:
懒汉模式(四种方法)
#include<iostream>
using namespace std;
//返回指针+指针分配空间 --需要释放对象指针
class A
{
public:
static A* GetA()
{
count++;
if (m_pA == nullptr)
m_pA = new A();
return m_pA;
}
void DeleteA() //释放对象指针
{
count--;
if (count==0&&m_pA != nullptr)
{
delete m_pA;
cout << "A对象指针被释放" << endl;
m_pA = nullptr;
}
};
private:
A(){};
~A(){};
static A* m_pA; //返回的对象指针
static int count;
};
A* A::m_pA = nullptr;
int A::count = 0;
//返回指针+静态局部变量 --不需要释放对象指针
class B
{
public:
static B* GetB()
{
static B m_B;
return &m_B;
}
private:
B(){};
};
//返回引用+静态局部变量 --不需要释放对象指针
class C
{
public:
static C& GetC()
{
static C m_C;
return m_C;
}
private:
C(){};
};
//返回引用+指针分配空间 --需要释放对象指针
class D
{
public:
static D& GetD()
{
count++;
if (m_pD == nullptr)
m_pD = new D();
return *m_pD;
}
void DeleteD() //释放对象指针
{
count--;
if (count==0&&m_pD != nullptr)
{
delete m_pD;
cout << "D对象指针被释放" << endl;
m_pD = nullptr;
}
};
private:
D(){};
~D(){};
static D* m_pD; //返回的对象指针
static int count;//引用计数
};
D* D::m_pD = nullptr;
int D::count = 0;
//主函数
int main()
{
A* a1 = A::GetA();
A* a2 = A::GetA();
A* a3 = a1;
cout <<"a1==a2:"<< (a1 == a2) << endl;
cout << "释放a1" << endl;
a1->DeleteA();
cout << "释放a2" << endl;
a2->DeleteA();
B* b1 = B::GetB();
B* b2 = B::GetB();
cout <<"b1==b2:"<< (b1 == b2) << endl;
C &c1 = C::GetC();
C &c2 = C::GetC();
cout <<"&c1==&c2"<< (&c1 == &c2) << endl;
D &d1 = D::GetD();
D &d2 = D::GetD();
cout << (&d1 == &d2) << endl;
cout << "释放d1" << endl;
d1.DeleteD();
cout << "释放d2" << endl;
d2.DeleteD();
cin.get();
return 0;
}最常用方法 :返回引用+静态局部变量
对GetA稍加修改,这个设计模板便可以适用于可变多实例情况,如一个类允许最多五个实例。**
存在的问题:
- 在使用指针法情况下,在多线程情况下若多个线程同时初始化对象 则会返回多个内存破坏单例模式的性质
解决方法:
- 进行加锁操作。在判断指针是否为空前加锁。分配后解锁
- 引入饿汉模式,在程序运行前给静态指针分配空间
懒汉模式
//只需要更改初始化静态成员的代码即可 A* A::m_pA = A::GetA();

京公网安备 11010502036488号