C++设计模式 —— 单例模式

news/2025/2/11 11:15:34/

设计模式 —— 单例模式

  • 一个问题
  • C++11后代写法
  • 单例模式的两种模式
    • 饿汉模式
    • 懒汉模式

在了解C++面向对象的三大特性:封装,继承,多态之后。我们创建类的时候就有了比较大的空间。但是,我们平时在创建类的时候,不是简单写个class和继承关系就完事了的。我们写的类要在一些场景下满足一些特殊的要求。

一个问题

现在有一个具体的要求,创建一个类,保证处处只有一个实体,这个要求在平常的工作中是很常见的, 配置文件管理,日志系统,数据库连接池, 线程池等。

如何实现呢?保证只有一个实体,可以考虑静态成员变量,我们之前在C++继承中说过,无论继承关系有多少层,只要为静态成员,全局就只有一份。所以我们可以先从这个方向入手。

//单例模式
class SingleClass
{
public:static SingleClass* GetInstance(){if (instance == nullptr){instance = new SingleClass();}return instance;}static SingleClass* instance;
};//静态成员在外部初始化
SingleClass* SingleClass::instance = nullptr;int main()
{SingleClass* s1 = SingleClass::GetInstance();SingleClass* s2 = SingleClass::GetInstance();if (s1 == s2){std::cout << "s1和s2为同一实体" << std::endl;}else{std::cout << "s1和s2不为同一实体" << std::endl;}}

在这里插入图片描述
这样看上去问题解决了,但是:
在这里插入图片描述
我们可以在类外创建对象,这不符合我们的要求,究其原因,我们把构造函数设为了公有解决这个问题将它声明为私有就可以了。

//单例模式
class SingleClass
{
public:static SingleClass* GetInstance(){if (instance == nullptr){instance = new SingleClass();}return instance;}
private:SingleClass(){} //构造函数为私有static SingleClass* instance;
};

在这里插入图片描述
这样就可以了,但是别忘了我们还有拷贝构造和赋值拷贝,这两个也可以构造出新对象,所以为了保险可以直接把他俩禁了:

//单例模式
class SingleClass
{
public:static SingleClass* GetInstance(){if (instance == nullptr){instance = new SingleClass();}return instance;}
private:SingleClass(){} //构造函数为私有SingleClass(const SingleClass&) = delete; // 禁止拷贝构造SingleClass& operator=(const SingleClass&) = delete; // 禁止赋值操作static SingleClass* instance;
};//静态成员在外部初始化
SingleClass* SingleClass::instance = nullptr;

这就是单例模式的雏形了。

单例模式(Singleton Pattern) 实现一个类保证处处只有一个实例。单例模式的核心思想是:

私有化构造函数:禁止外部直接创建对象。

静态方法获取实例:通过静态成员函数控制唯一实例的创建和访问。

禁止拷贝和赋值:防止通过拷贝构造函数或赋值操作生成新实例。

上面的代码还不能保证在多线程条件下是安全的的

//单例模式
class SingleClass
{
public:static SingleClass* GetInstance(){if (instance == nullptr){instance = new SingleClass();}return instance;}void PrintAddress(){std::cout << "地址为:" << this << std::endl;}
private:SingleClass(){} //构造函数为私有SingleClass(const SingleClass&) = delete; // 禁止拷贝构造SingleClass& operator=(const SingleClass&) = delete; // 禁止赋值操作static SingleClass* instance;//static std::mutex mtx;
};//静态成员在外部初始化
SingleClass* SingleClass::instance = nullptr;
//std::mutex SingleClass::mtx;// 全局互斥锁,用于保护输出
std::mutex coutMtx;// 线程函数
void threadFunc() 
{SingleClass* instance = SingleClass::GetInstance();std::lock_guard<std::mutex> lock(coutMtx);  // 加锁保护输出instance->PrintAddress();
}int main()
{const int threadNumber = 100;std::vector<thread> threadVT;//创建多个线程for (int i = 0; i < threadNumber; i++){threadVT.emplace_back(threadFunc);}for (auto& t : threadVT){t.join();}
}

大家可以试一下,可能会有不同的地址。为了保证线程安全,我们还得加锁

//单例模式
class SingleClass
{
public:static SingleClass* GetInstance(){if (instance == nullptr){std::lock_guard<std::mutex> lock(mtx);  // 加锁instance = new SingleClass();}return instance;}private:SingleClass(){} //构造函数为私有SingleClass(const SingleClass&) = delete; // 禁止拷贝构造SingleClass& operator=(const SingleClass&) = delete; // 禁止赋值操作static SingleClass* instance;static std::mutex mtx;
};//静态成员在外部初始化
SingleClass* SingleClass::instance = nullptr;
std::mutex SingleClass::mtx;

C++11后代写法

在 C++11 中,局部静态变量的初始化是线程安全的,因此可以简化代码

class SingleClass
{
public:static SingleClass& GetInstance(){static SingleClass instance;return instance;}
private:SingleClass() {};SingleClass(const SingleClass&) = delete; // 禁止拷贝构造SingleClass& operator=(const SingleClass&) = delete; // 禁止赋值操作
};int main()
{SingleClass& s1 = SingleClass::GetInstance();SingleClass& s2 = SingleClass::GetInstance();if (&s1 == &s2){std::cout << "s1和s2同一对象" << std::endl;}else{std::cout << "s1和s2不为同一对象" << std::endl;}
}

单例模式的两种模式

饿汉模式

饿汉模式讲究的是对象已经创建好,要用的时候直接拿就行

class SingleClass
{
public:static SingleClass* GetInstance(){return instance;}
private:SingleClass() {}SingleClass(const SingleClass&) = delete; // 禁止拷贝构造SingleClass& operator=(const SingleClass&) = delete; // 禁止赋值操作static SingleClass* instance;
};SingleClass* SingleClass::instance = new SingleClass();

懒汉模式

懒汉模式讲究的是对象要用时才创建

class SingleClass
{
public:static SingleClass* GetInstance(){if (instance == nullptr){instance = new SingleClass();return instance;}}
private:SingleClass() {}SingleClass(const SingleClass&) = delete; // 禁止拷贝构造SingleClass& operator=(const SingleClass&) = delete; // 禁止赋值操作static SingleClass* instance;
};SingleClass* SingleClass::instance = nullptr;

这个和我们最开始写的代码差不多,要改进的话还要保证线程安全。

我们可以将这种思想放到我们实际的代码中,比如我们日志系统的开发:

   class LoggerManager{public:static LoggerManager &getInstance(){// c++11之后,针对静态局部变量,编译器在编译的层面实现了线程安全// 当静态局部变量在没有构造完成之前,其他的线程进入就会阻塞static LoggerManager eton;return eton;}void addLogger(logs::logger::ptr &logger){if (hasLogger(logger->name()))return;std::unique_lock<std::mutex> lock(_mutex);_loggers.insert(std::make_pair(logger->name(), logger));}bool hasLogger(const std::string &name){std::unique_lock<std::mutex> lock(_mutex);auto it = _loggers.find(name);if (it == _loggers.end()){return false;}return true;}logs::logger::ptr getLogger(const std::string &name){std::unique_lock<std::mutex> lock(_mutex);auto it = _loggers.find(name);if (it == _loggers.end()){return logger::ptr();}return it->second;}logs::logger::ptr rootLogger(){return _root_logger;}private:LoggerManager(){std::unique_ptr<logs::LoggerBuilder> builder(new logs::LocalloggerBuild());builder->buildLoggerName("root");_root_logger = builder->build();_loggers.insert(std::make_pair("root", _root_logger));}private:std::mutex _mutex;logs::logger::ptr _root_logger; // 默认日志器std::unordered_map<std::string, logger::ptr> _loggers;};

http://www.ppmy.cn/news/1570838.html

相关文章

脚手架开发【实战教程】prompts + fs-extra

创建项目 新建文件夹 mycli_demo 在文件夹 mycli_demo 内新建文件 package.json {"name": "mycli_demo","version": "1.0.0","bin": {"mycli": "index.js"},"author": "","l…

网站快速收录策略:提升爬虫抓取效率

本文转自&#xff1a;百万收录网 原文链接&#xff1a;https://www.baiwanshoulu.com/102.html 要实现网站快速收录并提升爬虫抓取效率&#xff0c;可以从以下几个方面入手&#xff1a; 一、优化网站结构与内容 清晰的网站结构 设计简洁明了的网站导航&#xff0c;确保爬虫…

2021 年 9 月青少年软编等考 C 语言五级真题解析

目录 T1. 问题求解思路分析T2. 抓牛思路分析T3. 交易市场思路分析T4. 泳池思路分析T1. 问题求解 给定一个正整数 N N N,求最小的 M M M 满足比 N N N 大且 M M M 与 N N N 的二进制表示中有相同数目的 1 1 1。 举个例子,假如给定 N N N 为 78 78 78,二进制表示为 …

深入浅出:机器学习的全面解析

深入浅出&#xff1a;机器学习的全面解析 引言 机器学习&#xff08;Machine Learning, ML&#xff09;作为人工智能的一个重要分支&#xff0c;近年来取得了显著进展&#xff0c;并在多个领域中得到了广泛应用。本文将从基础概念、核心算法、应用场景以及未来发展趋势等方面…

快速理解一个Spring Boo项目的核心逻辑和架构

快速理解一个Spring Boo项目的核心逻辑和架构&#xff0c;可以遵循以下系统化的步骤&#xff1a; 1. 项目结构速览 标准目录结构&#xff1a; src/main/java&#xff1a;主代码&#xff08;含启动类、核心业务包&#xff09;src/main/resources&#xff1a;配置文件&#xff08…

关于JVM

本文分为两部分&#xff0c;一部分对JVM进行大致总结&#xff0c;第二部分为对周志明的JVM进行梳理 一&#xff1a;大致总结 首先利用上面这张图来大致说一下JVM&#xff1a;首先把一个.java文件编译为一个.class文件&#xff08;我们可以认为.java是用java写的&#xff0c;.c…

Git(分布式版本控制系统)系统学习笔记【并利用腾讯云的CODING和Windows上的Git工具来实操】

Git的概要介绍 1️⃣ Git 是什么&#xff1f; Git 是一个 分布式版本控制系统&#xff08;DVCS&#xff09;&#xff0c;用于跟踪代码的变更、协作开发和管理项目历史。 由 Linus Torvalds&#xff08;Linux 之父&#xff09;在 2005 年开发&#xff0c;主要用于 代码管理。…

嵌入式面试题 C/C++常见面试题整理_7

一.什么函数不能声明为虚函数? 常见的不能声明为虚函数的有:普通函数(非成员函数):静态成员函数;内联成员函数;构造函数;友元函数。 1.为什么C不支持普通函数为虚函数?普通函数(非成员函数)只能被overload&#xff0c;不能被override&#xff0c;声明为虚函数也没有什么意思…