C++设计模式——Singleton单例模式
一、单例模式的定义
单例模式,英文全称Singleton Pattern,是一种创建型设计模式,它保证一个类在程序中仅有一个实例,并对外提供一个访问的该类实例的全局接口。
单例模式通常用于需要控制对象资源的开发场景,一个类只创建一个对象的设计,既可以避免创建过多副本所造成的资源浪费现象,又可以避免引发数据一致性等问题。
在数据库连接、线程池设计、日志系统设计等开发场景,经常使用单例模式来创建对象,可以有效地降低对内存资源的占用。
在编码时,为了防止外部直接通过new关键字创建对象实例,Singleton类的构造函数必须是私有的。
在多线程开发场景,单例模式可以避免多个线程同时访问同一个资源,从而避免资源竞争的问题,如果还需要进一步保证线程安全性,可以在创建实例时添加同步锁。
单例模式在现实生活中的抽象实例:
电力公司:在一个城市或地区,通常只有一个电力公司负责供电,我们可以通过该公司来获取电力服务。
总统办公室:在一个国家,通常只有一个总统办公室负责国家事务处理,办公室负责确保国家事务的一致性和高效运作。
登录系统:用户只需要登录一次就可以打开多个应用程序的界面。
二、单例模式的结构
单例模式主要包含以下组件:
1.Singleton类:这是一个单例类,它在整个系统中只有一个实例。通常使用私有构造函数来创建、使用公共静态成员方法来访问。
2.私有构造函数:为了避免被外部实例化,Singleton类通常会定义一个私有构造函数,使得其他类无法通过调用构造函数创建Singleton的实例对象。
3.私有静态成员变量:Singleton类通常会声明一个私有静态成员变量,用来保存Singleton的唯一实例,且这个静态变量只能在Singleton类内部访问。
4.公共的对外接口:它通常被命名为getInstance(),通过该接口可以获取Singleton的唯一实例对象。该接口使用懒汉/饿汉的方式来实现,在接口内部会先判断实例是否已经存在,如果存在则直接返回,否则创建一个新的实例并返回。
组件之间的工作步骤如下:
1.饿汉模式的程序启动:在程序启动的同时就创建好Singleton实例,并提供全局唯一的外部访问接口,外部程序可以直接调用该接口来获取Singleton实例。
2.懒汉模式的程序启动:程序启动时并不会创建Singleton实例,程序在等到单例对象被第一次使用时才创建Singleton实例。
3.采用私有的静态变量存储已经创建好的Singleton实例。
4.外部客户端通过唯一的外部访问接口来访问并使用Singleton实例。
饿汉模式单例 & 懒汉模式单例:
饿汉模式(Singleton with Instantiation):
是一种线程安全的实现方法,在类初始化时就完成了单例实例的创建。
懒汉模式(Singleton with Lazy Initialization):
也被称为"双检锁"模式,只有当第一次真正需要获取单例实例时才进行单例实例的创建。
饿汉模式和懒汉模式都实现了单例,即保证在整个应用程序的生命周期中只有一个Singleton类实例。
饿汉模式的优点是线程安全,但缺点是如果该实例很复杂会增加初始化的耗时,从而导致程序的启动时间被延长。
懒汉模式的优点是延迟加载,可以节约资源和减少程序的启动耗时,缺点是需要考虑多线程环境下创建对象导致的线程安全问题,它通常在外部访问接口中使用双重检查锁定(Double-checked locking)来保证线程安全。
对应UML类图:
三、单例模式代码样例
1.单例模式的伪代码:
#include <iostream>
class Singleton {
private:
static Singleton* instance;
//私有构造函数,确保不能从外部实例化对象
Singleton() {}
public:
//获取单例实例的静态方法
static Singleton* getInstance() {
if (instance == nullptr) {
instance = new Singleton();
}
return instance;
}
};
Singleton* Singleton::instance = nullptr;
int main() {
Singleton* singleton = Singleton::getInstance();
return 0;
}
2.单例模式的加锁版伪代码:
class Singleton {
private:
static Singleton* instance;
Singleton() {} //私有化构造函数,防止外部创建实例
public:
static Singleton* getInstance() {
if (instance == nullptr) {
//在多线程环境下需要加锁保证只创建一个实例
// std::lock_guard<std::mutex> lock(mutex);
instance = new Singleton();
}
return instance;
}
//单例类的其他成员函数
void doSomething() {
// ...
}
};
Singleton* Singleton::instance = nullptr;
int main() {
Singleton* obj1 = Singleton::getInstance();
obj1->doSomething();
Singleton* obj2 = Singleton::getInstance();
obj2->doSomething();
delete obj1; //释放资源
return 0;
}
3.饿汉模式的伪代码:
class Singleton {
private:
static Singleton* instance;
//私有构造函数,防止外部直接创建对象
Singleton() {}
public:
//静态成员函数,用于获取单例对象
static Singleton* getInstance() {
return instance;
}
};
//提前创建好单例对象
Singleton* Singleton::instance = new Singleton();
4.懒汉模式的伪代码:
class Singleton {
private:
static Singleton* instance;
Singleton() {}
public:
//使用时才创建单例对象
static Singleton* getInstance() {
if (instance == nullptr) {
instance = new Singleton();
}
return instance;
}
};
Demo1:完整代码实现
#include <iostream>
class Singleton {
private:
static Singleton* instance;
Singleton() {}
public:
static Singleton* getInstance() {
if (instance == nullptr) {
instance = new Singleton();
}
return instance;
}
void showMessage() {
std::cout << "Hello, World!" << std::endl;
}
};
Singleton* Singleton::instance = nullptr;
int main() {
// 获取Singleton对象实例
Singleton* singleton = Singleton::getInstance();
// 调用对象的方法
singleton->showMessage();
return 0;
}
运行结果:
Hello, World!
Demo2:增加析构函数
#include <iostream>
class Singleton {
public:
static Singleton& getInstance()
{
//如果对象实例不存在就创建一个
if (!instance) {
instance = new Singleton();
}
return *instance;
}
//给外部调用的接口
void Operation()
{
std::cout
<< "Singleton is performing some operation."
<< std::endl;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
//私有化的构造函数
Singleton()
{
std::cout << "Singleton instance created."
<< std::endl;
}
//私有化的析构函数
~Singleton()
{
std::cout << "Singleton instance destroyed."
<< std::endl;
}
static Singleton* instance;
};
Singleton* Singleton::instance = nullptr;
int main()
{
Singleton& singleton = Singleton::getInstance();
singleton.Operation();
return 0;
}
运行结果:
Singleton instance created.
Singleton is performing some operation.
四、单例模式的优缺点
单例模式的优点:
可以有效限制资源的数量,对于那些需要全局访问的资源,单例模式可以保证该资源只有一个实例。
对象的创建/销毁过程最多只有一次,可以节省系统开销。
可以统一管理全局配置,如果单例模式的对象用来存储一些配置信息,可以实现对全局配置的管理。
简化了访问资源的入口,由于全局只有一个实例,使得客户端只需要关注一个访问入口即可。
单例模式的缺点:
缺乏灵活性,一旦创建了单例对象,就不能更改其实例化过程。
限制了一个类只能有一个实例,难以扩展该类的功能。
由于单例对象是全局可访问的,可能引发全局变量的滥用。
存在线程安全隐患,如果不增加一些锁机制,在多线程环境下可能会创建多个实例,影响单例的特性。
五、代码实战
#include <iostream>
using namespace std;
class Singleton
{
private:
static bool instanceFlag;
static Singleton* single;
Singleton()
{
printf("Private constructor finished.\n");
}
public:
static Singleton* getInstance();
void method();
~Singleton()
{
printf("Public de-constructor finished.\n");
instanceFlag = false;
}
};
bool Singleton::instanceFlag = false;
Singleton* Singleton::single = NULL;
Singleton* Singleton::getInstance()
{
if (!instanceFlag)
{
single = new Singleton();
instanceFlag = true;
return single;
}
else
{
return single;
}
}
void Singleton::method()
{
cout << "Method of the singleton class" << endl;
}
int main()
{
Singleton* sc1, * sc2;
sc1 = Singleton::getInstance();
sc1->method();
sc2 = Singleton::getInstance();
sc2->method();
delete sc1, sc2;
return 0;
}
运行结果:
Private constructor finished.
Method of the singleton class
Method of the singleton class
Public de-constructor finished.
六、参考阅读
https://www.geeksforgeeks.org/singleton-pattern-c-design-patterns/
https://scottlilly.com/c-design-patterns-the-singleton-pattern/
https://www.tutorialspoint.com/explain-cplusplus-singleton-design-pattern
https://www.codeproject.com/Articles/1921/Singleton-Pattern-its-implementation-with-Cplusplu