📋 前言
- 🖱 博客主页:在下马农的碎碎念
- 🤗 欢迎关注🔎点赞👍收藏⭐️留言📝
- ✍ 本文由在下马农原创,首发于CSDN
- 📆 首发时间:2023/8/25
- 📅 最近更新时间:2023/08/28
- 🤵 此马非凡马,房星本是星。向前敲瘦骨,犹自带铜声。
- 📇 系列文章目录: 暂无
- 🙏作者水平有限,如发现错误,请留言轰炸哦!万分感谢!
须知少年凌云志,曾许人间第一流
——清·吴庆坻《题三十小像》
以下是正文
一、什么是单例模式?
单例模式是一种常用的设计模式,它保证一个类只有一个实例,并且提供了全局访问该实例的方法。在C++中,单例模式的实现方式有多种。在单例模式中,通常使用一个静态方法或者一个静态变量来保存实例。这个静态方法或者静态变量可以被所有需要访问该实例的对象共享,并且在第一次调用时创建实例。之后每次调用该方法或者访问该变量时,都返回同一个实例。
单例模式的特点:
- 一个类只有一个实例
- 该实例在程序运行的整个周期内始终存在
- 该实例可以被全局访问
单例模式可以用于控制资源的访问,例如数据库连接池、线程池等。它还可以用来确保系统中某些组件只有一个实例,例如配置文件管理器、日志记录器等。
二、单例模式的实现方式
2.1 饿汉式:
特点:实现简单,线程安全,但可能造成内存浪费
适用情况: 单例对象在程序运行过程中频繁被调用
饿汉式是最简单的一种单例模式实现方式,它在程序启动时就创建了单例对象,因此也被称为“急切创建”方式。这种方式的优点是实现简单且线程安全,因为这种方式在单例对象被使用之前就已经创建好了,因此不存在多线程环境下的竞争问题,但是缺点是如果该对象很少被使用,会造成内存浪费。
饿汉式单例模式的实现方式一般是将单例模式定义为静态成员变量,并在类定义中就初始化它,这样单例对象就会在类装载的时候进行创建,在整个程序结束时按序销毁。
具体实现如下:
class Singleton {
public:
static Singleton* getInstance();
private:
Singleton();
~Singleton();
Singleton(const Singleton &signal);
const Singleton &operator=(const Singleton &signal);
private:
// 唯一的单例对象
static Singleton *instance_;
};
// 代码一运行就初始化创建实例 ,本身就线程安全
Singleton* Singleton::instance_= new (std::nothrow) Singleton();
Singleton* Singleton::getInstance() {
return instance_;
}
Singleton::Singleton() {}
Singleton::~Singleton() {}
在上面的示例代码中,Singleton类的instance_成员变量是一个静态成员变量,它被定义为私有的,只能通过getInstance()方法来访问。getInstance()方法返回的是instance_的引用,通过这种方式来保证只有一个实例被创建。由于instance_被定义为静态成员变量,它会在程序启动时就被初始化。由于该方式在程序启动时就创建了单例对象,因此被称为“饿汉式单例模式”。
2.2 懒汉式
特点:延迟创建对象实例,避免了不必要的资源消耗,但是在多线程环境中线程不安全,需要加锁保证线程安全
适用情况: 单例对象的创建和初始化过程比较耗时,而且在程序运行过程中可能并不总是需要使用该对象,对资源敏感时也不叫使用,如果线程安全没什么要求,也可以用
懒汉式是另一种常见的单例模式实现方式,它在第一次访问单例对象时才进行创建。具体实现如下:
头文件Singleton.h:
class Singleton {
private:
// 私有的构造函数,防止外部创建对象
Singleton();
// 单例对象的指针
static Singleton* instance;
public:
// 获取单例对象的静态方法
static Singleton* getInstance();
};
源文件Singleton.cpp
#include "Singleton.h"
Singleton* Singleton::instance = nullptr;
Singleton::Singleton() {
// 进行初始化操作
}
Singleton* Singleton::getInstance() {
if (instance == nullptr) {
instance = new Singleton();
}
return instance;
}
上述代码中,Singleton类的构造函数是私有的,防止外部直接创建对象。instance指针被初始化为nullptr。在getInstance()方法中,首先检查instance是否为nullptr,如果是,则说明还没有创建单例对象,需要进行创建。创建成功后,将其赋值给instance指针,并返回该指针。`
注意 :懒汉式单例模式的特点是在第一次请求时才创建对象,避免了程序启动时的资源浪费,但需要注意在多线程环境下的线程安全性。以上示例是简单的单线程示例,在多线程环境下需要添加线程安全的措施,比如使用互斥锁或双重检查锁定等机制来保证线程安全性。
2.3 双重检查锁
C++中的双检锁(Double-Checked Locking)实现单例模式是一种在多线程环境下延迟创建单例对象的方式,通过使用双重检查来提高性能。
头文件:
class Singleton {
public:
// 获取单例实例的静态方法
static Singleton* getInstance();
// 删除拷贝构造函数和赋值运算符重载,确保单例的唯一性
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
// 私有构造函数,防止外部实例化
Singleton();
static Singleton* instance; // 单例实例指针
static std::mutex mutex; // 互斥锁
};
源文件:
#include "Singleton.h"
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mutex;
Singleton::Singleton() {
// 构造函数
}
Singleton* Singleton::getInstance() {
if (instance == nullptr) {
std::lock_guard<std::mutex> lock(mutex); // 加锁
if (instance == nullptr) {
instance = new Singleton();
}
}
return instance;
}
注意: 双检锁(Double-Checked Locking)在某些情况下可能不是线程安全的,尤其是在特定的编译器和硬件平台上。这是由于编译器和处理器的优化行为可能导致双检锁失效,从而导致多个线程同时创建实例。
具体来说,双检锁的问题源于指令重排序(instruction reordering)和多核处理器的内存可见性(memory visibility)。编译器和处理器为了提高执行效率,可能会对代码中的指令进行重排序,而不考虑程序员的意图。这种重排序可能会导致在检查 instance 是否为 nullptr 之后,但在实际创建实例之前,另一个线程就已经读取到了一个尚未完全初始化的实例。
2.4 静态局部变量(推荐!!!实现简单,轻松易学)
使用静态局部变量的方式实现单例模式是一种简洁且线程安全的方法。无需显式使用互斥锁或原子操作,能够在需要时按需创建单例实例,并且在整个程序生命周期内保持单例的唯一性。
头文件:
class Singleton {
public:
static Singleton& getInstance();
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton();
};
源文件:
#include "Singleton.h"
Singleton& Singleton::getInstance() {
static Singleton instance;
return instance;
}
Singleton::Singleton() {
// 构造函数
}
在上述代码中,getInstance() 方法返回一个对静态局部变量 instance 的引用。静态局部变量在函数首次调用时被初始化,并且在整个程序生命周期内保持存在。由于静态局部变量在 C++ 中具有线程安全的保证,因此无需显式使用互斥锁或原子操作来保护实例的创建过程。
总结
以上就是C++单例模式的所有实现方式。不同的实现方式在性能、线程安全等方面有所区别,具体实现方式应该根据实际情况进行选择,同时,需要注意在多线程环境下进行线程安全的处理,推荐优先使用静态局部变量方式。