简单介绍
单例模式是一种创建型设计模式 | 让你能够保证一个类只有一个实例, 并提供一个访问该实例的全局节点。
它也会破坏代码的模块化特性,因为单例模式往往会承担了很多的职责,导致与 其他模块 产生过多的耦合
基础理解
单一职责原则:
- 一个类或模块应该只有一个引起它变化的原因。(确保了只有一个实例)
- 一个类或模块应该只负责一种功能或行为。(同上)
- 将不同的功能分离开来,避免将不相关的功能耦合在一起。
- (单例模式不满足,它同时会有许多职责)
单例模式同时解决了两个问题, 也违反了单一职责原则:
- 保证一个类只有一个实例:在需要控制共享资源权限的情况下就十分有用。你创建的所有实例对象都是 获得 第一次创建的实例对象,而不是一个新对象
- 为该实例提供一个全局访问节点:和全局变量一样,允许在程序的任何地方访问。 但是它可以保护该实例不被其他代码覆盖。(因为设置了私有)
UML 图
单例 (Singleton) 类声明了一个名为 getInstance
获取实例的静态方法来返回其所属类的一个相同实例。客户端只允许调用该方法获得实例
单例的构造函数必须对客户端 (Client) 代码隐藏(私有化) 。 调用 获取实例方法必须是获取单例对象的唯一方式。
实现方式
- 在类中添加一个私有静态成员变量用于保存单例实例。
- 声明一个公有静态构建方法用于获取单例实例。
- 在静态方法中实现"延迟初始化"。 该方法会在首次被调用时创建一个新对象, 并将其存储在静态成员变量中。 此后该方法每次被调用时都返回该实例。
- 将类的构造函数设为私有。 类的静态方法仍能调用构造函数, 但是其他对象不能调用。
c++ 11 中使用基本的饿汉单例模式是十分简单的, 将
GetInstance
设置为static即可,但此单例模式在多线程中会出错,所以需要执行加锁操作.
#include <iostream> // std::cout
#include <mutex> // std::mutex
#include <pthread.h> // pthread_create
/// 加锁的懒汉式实现 //
class SingleInstance
{
public:
// 获取单实例对象
static SingleInstance *&GetInstance(); // 重点
// 释放单实例,进程退出时调用
static void deleteInstance();
// 打印实例地址
void Print();
private:
// 将其构造和析构成为私有的, 禁止外部构造和析构
SingleInstance();
~SingleInstance();
// 将其拷贝构造和赋值构造成为私有函数, 禁止外部拷贝和赋值
SingleInstance(const SingleInstance &signal);
const SingleInstance &operator=(const SingleInstance &signal);
private:
// 唯一单实例对象指针
static SingleInstance *m_SingleInstance;
static std::mutex m_Mutex;
};
// 初始化静态成员变量
SingleInstance *SingleInstance::m_SingleInstance = NULL;
std::mutex SingleInstance::m_Mutex;
SingleInstance *&SingleInstance::GetInstance()
{
// 这里使用了两个 if判断语句的技术称为双检锁;好处是,只有判断指针为空的时候才加锁,
// 避免每次调用 GetInstance的方法都加锁,锁的开销毕竟还是有点大的。
if (m_SingleInstance == NULL)
{
std::unique_lock<std::mutex> lock(m_Mutex); // 加锁
if (m_SingleInstance == NULL)
{
m_SingleInstance = new (std::nothrow) SingleInstance; // 第一次为空时创建 以后直接返回第一次创建的实例
}
}
return m_SingleInstance;
}
void SingleInstance::deleteInstance()
{
std::unique_lock<std::mutex> lock(m_Mutex); // 加锁
if (m_SingleInstance)
{
delete m_SingleInstance;
m_SingleInstance = NULL;
}
}
void SingleInstance::Print()
{
std::cout << "我的实例内存地址是:" << this << std::endl;
}
SingleInstance::SingleInstance()
{
std::cout << "构造函数" << std::endl;
}
SingleInstance::~SingleInstance()
{
std::cout << "析构函数" << std::endl;
}
/// 加锁的懒汉式实现 //
void *PrintHello(void *threadid)
{
// 主线程与子线程分离,两者相互不干涉,子线程结束同时子线程的资源自动回收
pthread_detach(pthread_self());
// 对传入的参数进行强制类型转换,由无类型指针变为整形数指针,然后再读取
int tid = *((int *)threadid);
std::cout << "Hi, 我是线程 ID:[" << tid << "]" << std::endl;
// 打印实例地址
SingleInstance::GetInstance()->Print();
pthread_exit(NULL);
return NULL;
}
#define NUM_THREADS 5 // 线程个数
int main()
{
pthread_t threads[NUM_THREADS] = {0};
int indexes[NUM_THREADS] = {0}; // 用数组来保存i的值
int ret = 0;
int i = 0;
std::cout << "main() : 开始 ... " << std::endl;
for (i = 0; i < NUM_THREADS; i++)
{
std::cout << "main() : 创建线程:[" << i << "]" << std::endl;
indexes[i] = i; // 先保存i的值
// 传入的时候必须强制转换为void* 类型,即无类型指针
ret = pthread_create(&threads[i], NULL, PrintHello, (void *)&(indexes[i]));
if (ret)
{
std::cout << "Error:无法创建线程," << ret << std::endl;
exit(-1);
}
}
// 手动释放单实例的资源
SingleInstance::deleteInstance();
std::cout << "main() : 结束! " << std::endl;
system("pause");
}
应用场景
使用场景就是它的两个特性:
- 如果某个类对于所有客户端只有一个可用的实例
单例模式返回的永远只有第一个创建的实例对象,
- 更加严格地控制全局变量
除了单例类自己以外, 无法通过任何方式替换的第一个创建的实例。可以修改
getInstance
调整单例实例的数量
与其他模式的关系
-
外观模式类通常可以转换为单例模式类, 因为在大部分情况下一个外观对象就足够了。
-
抽象工厂模式、 生成器模式和原型模式都可以用单例来实现。
-
如果你能将对象的所有共享状态简化为一个享元对象, 那么享元模式就和单例类似了。 但这两个模式有两个根本性的不同。
- 只会有一个单例实体, 但是享元类可以有多个实体, 各实体的内在状态也可以不同。
- 单例对象可以是可变的。 享元对象是不可变的。
优缺点
优点 | 缺点 |
---|---|
你可以保证一个类只有一个实例。 | 违反了单一职责原则。 该模式同时解决了两个问题。 |
你获得了一个指向该实例的全局访问节点。 | 单例模式可能掩盖不良设计, 比如程序各组件之间相互了解过多等 |
仅在首次请求单例对象时对其进行初始化。 | 该模式在多线程环境下需要进行特殊处理, 避免多个线程多次创建单例对象。 |
单例的客户端代码单元测试可能会比较困难, 因为许多测试框架以基于继承的方式创建模拟对象。 由于单例类的构造函数是私有的, 而且绝大部分语言无法重写静态方法, 所以你需要想出仔细考虑模拟单例的方法。 要么干脆不编写测试代码, 或者不使用单例模式。 |
单例模式被视为一种反模式。 因此它在 C++ 代码中的使用频率正在减少。不过还是有用的.使用好它只需要记住它的两个特点即可文章来源:https://www.toymoban.com/news/detail-848416.html
如果有错还望指正。有什么建议也可以留言。
参考文章文章来源地址https://www.toymoban.com/news/detail-848416.html
到了这里,关于C++ 【单例模式】的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!