什么是单例模式?
单例模式(Singleton Pattern)是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来访问这个实例。这种模式在需要控制资源的访问、管理全局状态或避免创建多个实例的场景中非常有用。
注意:
- 1、单例类只能有一个实例。
- 2、单例类必须自己创建自己的唯一实例。
- 3、单例类必须给所有其他对象提供这一实例。
单例模式的适用场景
- 资源管理:例如,线程池、数据库连接池等资源需要全局唯一的实例来进行管理和控制。
- 全局状态:需要一个全局唯一的对象来保存状态,如配置文件读取器、日志记录器等。
- 避免多次实例化:某些对象的实例化过程耗时耗资源,需要保证只创建一次。
单例模式同时解决了两个问题, 所以违反了单一职责原则:
-
保证一个类只有一个实例。 为什么会有人想要控制一个类所拥有的实例数量? 最常见的原因是控制某些共享资源 (例如数据库或文件) 的访问权限。
它的运作方式是这样的: 如果你创建了一个对象, 同时过一会儿后你决定再创建一个新对象, 此时你会获得之前已创建的对象, 而不是一个新对象。
注意, 普通构造函数无法实现上述行为, 因为构造函数的设计决定了它必须总是返回一个新对象。
客户端甚至可能没有意识到它们一直都在使用同一个对象。
2 . 为该实例提供一个全局访问节点。 还记得你 (好吧, 其实是我自己) 用过的那些存储重要对象的全局变量吗? 它们在使用上十分方便, 但同时也非常不安全, 因为任何代码都有可能覆盖掉那些变量的内容, 从而引发程序崩溃。
和全局变量一样, 单例模式也允许在程序的任何地方访问特定对象。 但是它可以保护该实例不被其他代码覆盖。
还有一点: 你不会希望解决同一个问题的代码分散在程序各处的。 因此更好的方式是将其放在同一个类中, 特别是当其他代码已经依赖这个类时更应该如此。
主要解决:一个全局使用的类频繁地创建与销毁。
何时使用:当您想控制实例数目,节省系统资源的时候。
如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
关键代码:构造函数是私有的。
模式结构:
单例模式的核心要素
- 私有化构造函数:防止其他代码使用
new
关键字直接实例化对象。 - 静态成员变量:持有类的唯一实例。
- 公有的静态方法:提供全局访问点来获取该实例。
C++实现单例模式
在C++中实现单例模式时,需要考虑线程安全性、内存管理和效率等问题。以下是单例模式的几种常见实现方式。
1. 饿汉式单例
饿汉式单例在类加载时就初始化单例对象。它的实现简单且线程安全,但如果单例对象的初始化依赖于其他资源或配置文件,这种方式可能不太适合。
class Singleton {
private:
static Singleton* instance;
Singleton() {} // 私有构造函数
public:
static Singleton* getInstance() {
return instance;
}
};
// 静态成员变量初始化
Singleton* Singleton::instance = new Singleton();
int main() {
Singleton* s1 = Singleton::getInstance();
Singleton* s2 = Singleton::getInstance();
// 验证s1和s2是同一个实例
if (s1 == s2) {
std::cout << "s1和s2是同一个实例" << std::endl;
} else {
std::cout << "s1和s2不是同一个实例" << std::endl;
}
return 0;
}
2. 懒汉式单例(线程不安全)
懒汉式单例在第一次调用getInstance
方法时初始化实例。这种方式延迟了实例的创建,但在多线程环境下不安全。
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* s1 = Singleton::getInstance();
Singleton* s2 = Singleton::getInstance();
// 验证s1和s2是同一个实例
if (s1 == s2) {
std::cout << "s1和s2是同一个实例" << std::endl;
} else {
std::cout << "s1和s2不是同一个实例" << std::endl;
}
return 0;
}
3. 线程安全的懒汉式单例
为了解决懒汉式单例在多线程环境中的问题,可以使用互斥锁来确保线程安全。
#include <mutex>
class Singleton {
private:
static Singleton* instance;
static std::mutex mtx;
Singleton() {} // 私有构造函数
public:
static Singleton* getInstance() {
std::lock_guard<std::mutex> lock(mtx);
if (instance == nullptr) {
instance = new Singleton();
}
return instance;
}
};
// 静态成员变量初始化
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mtx;
int main() {
Singleton* s1 = Singleton::getInstance();
Singleton* s2 = Singleton::getInstance();
// 验证s1和s2是同一个实例
if (s1 == s2) {
std::cout << "s1和s2是同一个实例" << std::endl;
} else {
std::cout << "s1和s2不是同一个实例" << std::endl;
}
return 0;
}
4. 使用C++11的线程安全单例
C++11引入了std::call_once
和std::once_flag
,可以简化线程安全的单例模式实现。
#include <mutex>
class Singleton {
private:
static Singleton* instance;
static std::once_flag initInstanceFlag;
Singleton() {} // 私有构造函数
public:
static Singleton* getInstance() {
std::call_once(initInstanceFlag, []() {
instance = new Singleton();
});
return instance;
}
};
// 静态成员变量初始化
Singleton* Singleton::instance = nullptr;
std::once_flag Singleton::initInstanceFlag;
int main() {
Singleton* s1 = Singleton::getInstance();
Singleton* s2 = Singleton::getInstance();
// 验证s1和s2是同一个实例
if (s1 == s2) {
std::cout << "s1和s2是同一个实例" << std::endl;
} else {
std::cout << "s1和s2不是同一个实例" << std::endl;
}
return 0;
}
单例模式的优缺点
优点
- 唯一实例:确保系统中只有一个实例,节省资源。
- 全局访问:提供一个全局访问点,方便访问实例。
- 延迟初始化:某些实现方式支持延迟初始化,优化性能。
缺点
- 全局状态:可能引入全局状态,增加系统复杂性。
- 并发问题:在多线程环境下实现单例模式需要考虑线程安全性。
- 难以测试:单例模式可能导致代码难以测试,因为它隐藏了类的依赖关系。
总结
单例模式是一种非常常用的设计模式,适用于需要全局唯一实例的场景。在C++中,可以通过多种方式实现单例模式,包括饿汉式单例、懒汉式单例和线程安全的单例实现。每种实现方式都有其优缺点,开发者需要根据具体需求选择合适的实现方式。