单例九品--第九品[可用的设计]

单例九品--第九品[可用的设计]

  • 上一品引入
  • 写在前边
  • 代码部分
  • 实现方式的评注和思考
  • 写在最后

上一品引入

自第五品以来,为解决第四品的静态初始化灾难问题,将全局对象设置为指针类型,但是指针是有被修改的风险。所以第八品将全局单例对象封装在sing类的内部,写为私有类型,并提供了一个调用接口,完成单例对象引用绑定。第八品的代码设计思路已经是可以使用的了,但是代码比较混乱复杂,不便于拓展。第九品将实现代码的易拓展性。

写在前边

  • 基本思路
    • 对单例类的功能逻辑与单例逻辑进行划分,分别放入不同的部分
    • 使用CRTP模拟“基类-派生类”行为
  • 优点
    • 更容易支持不同的单例实例
    • 使用模板不会引入运行期成本

代码部分

5个文件: sing_temp.h sing1.h sing2.h main.cpp src.cpp

  • sing_temp.cpp
#pragma once
#include <atomic>template <typename T>
class SingTemp
{
public:struct Init{Init(){auto& count = RefCount();auto ori = count.fetch_add(1);if (ori == 0){T* ptr = SingTemp::Ptr();   // new (ptr) T();              // placement new的构造方式}}~Init(){auto& count = RefCount();auto ori = count.fetch_sub(1);if (ori == 1){T* ptr = SingTemp::Ptr();ptr->~T();}}static auto& RefCount(){static std::atomic<unsigned> count{ 0 };return count;}Init(const Init&) = delete;Init& operator= (const Init&) = delete;};protected:SingTemp() = default;~SingTemp() = default;SingTemp(const SingTemp&) = delete;SingTemp& operator= (const SingTemp&) = delete;public:static T* Ptr(){alignas(T) static char singBuf[sizeof(T)];  // 编译器知道有一块内存要分给char类型数组return reinterpret_cast<T*>(singBuf);       // char类型数组指针强制转换为T*类,并返回这块内存的首地址。供派生类去引用绑定}
};
  • sing1.h
#pragma once
#include "sing_temp.h"
#include <iostream>class Sing1 : public SingTemp<Sing1>
{friend SingTemp<Sing1>;   // 为了基类中调用派生类的析构函数和构造函数,或者不写这句话,直接把构造函数和析构函数写成public(不安全)private:Sing1(): SingTemp<Sing1>(){std::cout << "Sing1 construct\n";val = 100;}~Sing1(){std::cout << "Sing1 destroy\n";}public:int val;
};static Sing1::Init sing1Init;
static Sing1& singleton1 = *Sing1::Ptr(); 
  • sing2.h
#pragma once
#include "sing_temp.h"
#include "sing1.h"
#include <iostream>class Sing2 : public SingTemp<Sing2>
{friend SingTemp<Sing2>;private:Sing2(): SingTemp<Sing2>(){std::cout << "Sing2 construct\n";val = singleton1.val + 1;    // 使用sing1类实现 sing2的构造初始化}~Sing2(){std::cout << "Sing2 destroy\n";}public:int val;
};static Sing2::Init sing2Init;
static Sing2& singleton2 = *Sing2::Ptr();
  • main.cpp
#include "sing2.h"
#include "sing1.h"void fun();
int main(int argc, char** argv)
{std::cout << "from main: " << singleton1.val << '\n';std::cout << "from main: " << singleton2.val << '\n';fun();
}
  • src.cpp
#include "sing1.h"
#include "sing2.h"
#include <iostream>void fun()
{std::cout << "from fun: " << singleton2.val << '\n';std::cout << "from fun: " << singleton1.val << '\n';
}
  • output
Sing1 construct
Sing2 construct
from main: 100
from main: 101
from fun: 101
from fun: 100
Sing2 destroy
Sing1 destroy

实现方式的评注和思考

  1. sing_temp.h是一个基类,sing1.h和sing2.h是基类的派生类。sing_temp.h中完成了初始化子类的构造逻辑,并提供了一个全局对象的静态函数访问接口,也就是完成了单例逻辑的封装。sing1.h和sing2.h封存了各自的功能逻辑。

  2. 派生类对象中将基类定义为派生类的友元类,因为基类中需要使用派生类中的析构函数和构造函数,如果不将基类定义为派生类的友元类,那么基类没有调用派生类中私有函数的权限。

  3. 基类sing_temp的init函数结合计数逻辑控制单例的初次构造时机和销毁时机,使用placement new的方式完成首次访问的单例(对应派生类)构造,并提供一个调用函数接口。其中placement new实现的实例构造和销毁的时候,分别调用的是对应派生类中的构造函数和析构函数,也就是说基类调用了派生类的私有成员函数(因为为了安全,析构函数和构造函数写为了私有)。 因此,需要在派生类sing.h中将基类写为友元类,这样基类才能调用派生类中的成员函数

  4. CRTP模式实现了基类调用派生类中私有函数的方法。不使用CRTP方法的时候,一般的实现思路就是在基类中使用虚函数,然后在派生类中重写。通过CRTP可以使得类具有类似于虚函数的效果,同时又没有虚函数调用时的开销(虚函数调用需要通过虚函数指针查找虚函数表进行调用),同时类的对象的体积相比使用虚函数也会减少(不需要存储虚函数指针),但是缺点是无法动态绑定。

一个关奇异递归模板模式(Curiously Recurring Template Pattern)的帖子的链接

写在最后

第三品与第九品都是可以使用的设计思路,但是第三品因为每次使用到单例都要进入instance函数判断单例是否已经被构造,所以引入了多线程的时间损耗。第九品相对第三品而言,在本专栏的中的,如果在第三品和第九品的main函数加入下边的代码:

size_t res = 0;for (unsigned i = 0; i < 999999999; ++i){res += singleton1.val + i;}

就可以看出,第九品的运算次数是第三品运算次数的30%。性能上有了很大的提升。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/735466.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2024.02.07 校招 实习 内推 面经

绿*泡*泡VX&#xff1a; neituijunsir 交流*裙 &#xff0c;内推/实习/校招汇总表格 1、校招 | 腾讯TEG 2024 校招开放全新机会&#xff0c;持续热招&#xff08;内推&#xff09; 校招 | 腾讯TEG 2024 校招开放全新机会&#xff0c;持续热招&#xff08;内推&#xff09; …

【MATLAB】MATLAB学习笔记

MATLAB入门 基础操作变量命名数据类型逻辑和流程控制循环结构分支结构 绘图基本操作二维平面绘图绘图参数三位立体绘图图像窗口的分割 本文参考B站视频&#xff1a;BV13D4y1Q7RS 由于我对于C语言很熟悉&#xff0c;很多语法是会参考C来学 基础操作 清屏%% 清空环境变量及命令 …

图腾柱PFC工作原理:一张图

视屏链接&#xff1a; PFC工作原理

Java SE入门及基础(33)

final 修饰符 1. 应用范围 final 修饰符应该使用在类、变量以及方法上 2. final 修饰类 Note that you can also declare an entire class final. A class that is declared final cannot be subclassed. This is particularly useful, for example, when creating an imm…

docker学习笔记——Dockerfile

Dockerfile是一个镜像描述文件&#xff0c;通过Dockerfile文件可以构建一个属于自己的镜像。 如何通过Dockerfile构建自己的镜像&#xff1a; 在指定位置创建一个Dockerfile文件&#xff0c;在文件中编写Dockerfile相关语法。 构建镜像&#xff0c;docker build -t aa:1.0 .(指…

Effective C++ 学习笔记 条款22 将成员变量声明为private

下面是作者的规划。首先带你看看为什么成员变量不该是public&#xff0c;然后让你看看所有反对public成员变量的论点同样适用于protected成员变量。最后导出一个结论&#xff1a;成员变量应该是private。获得这个结论后&#xff0c;本条款也就大功告成了。 好&#xff0c;现在…

【Vue】生命周期

Vue生命周期 就是一个Vue实例从创建 到 销毁 的整个过程 生命周期四个阶段 1.创建阶段&#xff1a;创建响应式数据 2.挂载阶段&#xff1a;渲染模板 3.更新阶段&#xff1a;修改数据&#xff0c;更新视图 4.销毁阶段&#xff1a;销毁Vue实例 生命周期钩子 Vue生命周期过…

【每日一题】2834. 找出美丽数组的最小和-2024.3.8

题目&#xff1a; 2834. 找出美丽数组的最小和 给你两个正整数&#xff1a;n 和 target 。 如果数组 nums 满足下述条件&#xff0c;则称其为 美丽数组 。 nums.length n.nums 由两两互不相同的正整数组成。在范围 [0, n-1] 内&#xff0c;不存在 两个 不同 下标 i 和 j &…

阿里云实现两个VPC网络资源互通

背景 由于实际项目预算有限&#xff0c;两套环境虽然分别属于不同的专有网络即不同的VPC&#xff0c;但是希望借助一台运维机器实现对两个环境的监控和日常的运维操作 网络架构 如下是需要实现的外网架构图&#xff0c;其中希望实现UAT环境的一台windows的堡垒机可以访问生产…

前端软件工程师100问?

作为一个前端软件工程师&#xff0c;可能会遇到的问题非常多&#xff0c;以下是我为您精选的100个常见问题&#xff1a; 以上100个问题涵盖了前端开发的基础知识、框架应用、性能优化、安全性、兼容性、前沿技术等多个维度。作为前端软件工程师&#xff0c;了解这些问题有助于提…

第G3周:CGAN入门|生成手势图像

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 | 接辅导、项目定制 一、前置知识 CGAN&#xff08;条件生成对抗网络&#xff09;的原理是在原始GAN的基础上&#xff0c;为生成器和判别器提供 额外的条件信息…

【linux】04 :linix实用操作

1.常用快捷键 ctrlc表示强制停止。linux某些程序的运行&#xff0c;如果想强制停止&#xff0c;可以使用&#xff1b;命令输入错误&#xff0c;也可以通过ctrlc,退出当前输入&#xff0c;重新输入。 ctrld表示退出登录&#xff0c;比如退出root以回到普通用户&#xff0c;或者…

Stable Diffusion 模型下载:ZavyChromaXL(现实、魔幻)

本文收录于《AI绘画从入门到精通》专栏&#xff0c;专栏总目录&#xff1a;点这里。 文章目录 模型介绍生成案例案例一案例二案例三案例四案例五案例六案例七案例八 下载地址 模型介绍 作者述&#xff1a;该模型系列应该是用于 SDXL 的 ZavyMix SD1.5 模型的延续。主要重点是获…

背包问题算法

背包问题算法 0-1背包问题二维数组一维数组 完全背包问题二维数组一维数组 多重背包问题一维数组 0-1背包问题 问题&#xff1a;背包的容量为9&#xff0c;有重量分别为[2, 4, 6, 9]的四个物品&#xff0c;价值分别为[3, 4, 5, 6]&#xff0c;求背包能装的物品的最大价值是多少…

Orange3数据预处理(预处理器组件)

1.组件介绍 Orange3 提供了一系列的数据预处理工具&#xff0c;这些工具可以帮助用户在数据分析之前准备好数据。以下是您请求的预处理组件的详细解释&#xff1a; Discretize Continuous Variables&#xff08;离散化连续变量&#xff09;&#xff1a; 这个组件将连续变量转…

个人网站展示(静态)

大学期间做了一个个人博客网站&#xff0c;纯H5编码的网站&#xff0c;利用php搭建了一个留言模块。 有需要源码的同学&#xff0c;可以联系我~ 首页&#xff1a; IT杂记模块 文人墨客模块 劳有所获模块 生活日志模块 关于我 一个推崇全栈开发的前端开发人员 微信: itrzzh …

elasticsearch篇

1.初识elasticsearch 1.1.了解ES 1.1.1.elasticsearch的作用 elasticsearch是一款非常强大的开源搜索引擎&#xff0c;具备非常多强大功能&#xff0c;可以帮助我们从海量数据中快速找到需要的内容 例如&#xff1a; 在电商网站搜索商品 在百度搜索答案 在打车软件搜索附近…

代码随想录算法训练营Day39 || leetCode 762.不同路径 || 63. 不同路径 II

62.不同路径 每一位的结果等于上方与左侧结果和 class Solution { public:int uniquePaths(int m, int n) {vector<vector<int>> dp(m,vector(n,0));for (int i 0; i < m; i) dp[i][0] 1;for (int j 0; j < n; j) dp[0][j] 1;for (int i 1; i < m; …

使用docker部署redis集群

编写脚本 批量创建目录文件&#xff0c;编写配置文件 [rootlocalhost ~]# cat redis.sh #/bin/bash for port in $(seq 1 6); do mkdir -p /mydata/redis/node-${port}/conf touch /mydata/redis/node-${port}/conf/redis.conf cat << EOF >>/mydata/redis/node-…

安卓开发面试题

安卓开发面试题 解释一下 Android 中的四大组件。 答&#xff1a;Android 中的四大组件是 Activity、Service、BroadcastReceiver 和 ContentProvider。其中&#xff0c;Activity 负责界面展示和与用户交互&#xff1b;Service 负责后台服务处理&#xff1b;BroadcastReceiver …