一、单例模式的定义
单例模式,英文全称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