当前位置: 首页 > news >正文

单例九品--第五品

单例九品--第五品

  • 上一品引入
  • 写在前边
  • 代码部分1
  • 代码部分2
  • 实现方式评注与思考
  • 下一品的设计思考

上一品引入

第四品中可能会因为翻译单元的链接先后顺序,造成静态初始化灾难的问题。造成的原因是因为存在调用单例对象前没有完成定义的问题,这一品将着重解决这个问题。

写在前边

  • 基本思路
    • 引入初始化类
    • 初始化类是单例类的子类,可以访问单例类的所有成员
    • 通过初始化类的实例作为纽带,一定程度上控制初始化顺序
  • 优点
    • 初始化类可以精确控制初始化时机
  • 缺点
    • 似乎可以解决 static initialization order fiasco 问题,但实际上程序包含了更深层次的
    隐患:可能出现未定义的行为

代码部分1

三个文件: main.cpp, sing.cpp和sing.h

  • main.cpp
#include "sing.h"static Sing::Init init; auto singletonInst2 = singletonInst->val;int main(int argc, char** argv)
{std::cout << "get value: " << singletonInst2 << '\n';
}
  • sing.cpp
#include "sing.h"
#include <memory>
#include <iostream>std::unique_ptr<Sing> singletonInst;Sing::Init::Init()
{if (!singletonInst){singletonInst.reset(new Sing()); }
}
  • sing.h
#pragma once
#include <iostream>
#include <memory>class Sing
{
public:struct Init{Init();Init(const Init&) = delete;Init& operator= (const Init&) = delete;};public:~Sing(){std::cout << "Sing destroy\n";}private:Sing(){std::cout << "Sing construct\n";val = 100;}Sing(const Sing&) = delete;Sing& operator= (const Sing&) = delete;
public:int val;
};extern std::unique_ptr<Sing> singletonInst; // 声明,把sing.cpp中的定义暴露给main.cpp// init为sing类的子类
  • output

编译链接运行方式1
g++ -c ./main.cpp -std=c++20 (-std参数可选)
g++ -c ./sing.cpp -std=c++20 (-std参数可选)
g++ main.o sing.o -o ./ms
./ms

Sing construct
get value: 100
Sing destroy

编译链接运行方式2
g++ -c ./main.cpp -std=c++20 (-std参数可选)
g++ -c ./sing.cpp -std=c++20 (-std参数可选)
g++ sing.o main.o -o ./sm
./sm

Sing construct
get value: 100
Sing destroy

从上边结果可以看出,无论什么样的链接顺序,都可以正常运行,那是不是这种实现方式已经完全没有问题了? 不是的,下边的变种将会说明这一点!!!

代码部分2

三个文件: main.cpp sing.cpp 和sing.h

  • main.cpp
#include "sing.h"static Sing::Init init;
auto singletonInst2 = singletonInst->val;int main(int argc, char** argv)
{std::cout << "get value: " << singletonInst2 << '\n';std::cout << singletonInst.get() << std::endl;std::cout << singletonInst->val << std::endl;return 0;
}
  • sing.cpp
#include "sing.h"
#include <memory>
#include <iostream>MyUniquePtr<Sing> singletonInst;Sing::Init::Init()
{if (!singletonInst){singletonInst.reset(new Sing());}
}
  • sing.h
#pragma once
#include <iostream>
#include <memory>class Sing
{
public:struct Init{Init();Init(const Init&) = delete;Init& operator= (const Init&) = delete;};public:~Sing(){std::cout << "Sing destroy\n";}private:Sing(){std::cout << "Sing construct\n";val = 100;}Sing(const Sing&) = delete;Sing& operator= (const Sing&) = delete;
public:int val;
};template <typename T>
class MyUniquePtr : public std::unique_ptr<T>
{
public:MyUniquePtr() : std::unique_ptr<T>() {}
};extern MyUniquePtr<Sing> singletonInst;
  • output

编译链接运行方式1:
g++ -c ./main.cpp
g++ -c ./sing.cpp
g++ main.o sing.o -o ./ms

Sing construct
get value: 100
0
Segmentation fault (core dumped)  段错误

编译链接运行方式2:
g++ -c ./main.cpp
g++ -c ./sing.cpp
g++ sing.o main.o -o ./sm

Sing construct
get value: 100
0x56457bcd1eb0
100
Sing destroy

这个例子与上一个例子的实现方式是一致的,不同点在于单例对象的类型不同,前者对象是unique_ptr类型,后者是unique_ptr的派生类型。然而就是因为使用了unique_ptr的派生类型MyUniquePtr,就出现了链接的时候翻译单元main.cpp在前,sing.cpp在后时,出现了单例调用前未定义的问题。

这是什么原因呐?
在解释这个问题之前需要明白不同类型对象的初始化时间和初始化类型,在这里辨析编译期初始化,零初始化和缺省初始化的区别:

  1. 编译期初始化: c++中常见的编译器初始化包括但不限于常量表达式(constexpr, 凡是被constexpr修饰的函数和变量,都可以在编译期实现初始化,但是具体也要取决于编译器的类型,因为有些编译器会选择在运行初始化constexpr),枚举类型enum和模板元编程等。也就是在编译期就完成了计算,并将对应的结果存起来,在运行期的时候直接使用。
    编译期初始化,分为两种,constexpr和consteval,constexpr修饰的函数,意思是可以在编译期被调用,也可以选在在运行期被调用,但是具体什么时候被调用,是取决于使用的编译器种类consteval修饰的函数,意思是可以编译器被调用,并且必须在编译期调用。如果定义一个实例对象,这个对象的构造函数被consteval修饰,那么一定在编译期系统就会计算出这个对象的值存起来,在运行的时候直接完成两者的联系。

  2. 零初始化: 指在变量声明时将其初始化为零或默认值的行为。在C++中,如果没有显式提供初始化值,那么内置类型的变量将被初始化为零,而自定义类型的变量将调用其默认构造函数进行初始化。

  3. 缺省初始化: 缺省初始化与零初始化在自定义类型上的行为一致,也会调用默认构造函数来初始化成员变量。如果成员变量没有在构造函数中显式初始化,那么它们将保持未定义的状态。

运行期初始化与编译期初始化
对于在运行期才初始化的对象,采用的初始化方式是零初始化和缺省初始化。在编译的时候,系统首先计算这个对象所需的内存空间,然后将这块内存里边的所有内容都改为0,这就完成了零初始化的过程,也就是说这个实例对象指向了内容全为0的一块内存A。随后在运行的时候,当轮到该对象的定义初始化的时候,然后进行该对象的缺省初始化,缺省初始化会让该对象指向悬空,也就是指针P没有具体的指向区域,是个还没有分配指向的空指针。

例子2中,如果链接的时候main.o在前,sing.o在后。因为singlentonInst的类型是unique_ptr的拓展类,构造函数不是constexpr类型的(因此是运行期初始化,进行零初始化和缺省初始化的操作)。所以编译器首先完成了sing.cpp中的对象singlentoninst的零初始化,也就是是这个对象指向一开内存都是0的空间。然后在mian.cpp中调用init,使得对象singlentonInst指向一块新的有内容的区域(其中有val=100)。然后进入sing.o完成singlentonInst的缺省初始化,使得对象悬空(未定义,指针未分配),所以指向为空(0),输出singletonInst->val的时候出现段错误。

例子1不会因为链接顺序出问题是因为抽象类型unique_ptr的默认构造函数被constexpr修饰,g++编译默认constexpr类型构造函数在编译期被调用,实现了对象的编译期初始化。
在这里插入图片描述

实现方式评注与思考

  1. 对于在运行期才初始化的对象,采用的初始化方式是零初始化和缺省初始化。在编译的时候,系统首先计算这个对象所需的内存空间,然后将这块内存里边的所有内容都改为0,这就完成了零初始化的过程,也就是说这个实例对象指向了内容全为0的一块内存A。随后在运行的时候,当轮到该对象的定义初始化的时候,然后进行该对象的缺省初始化,缺省初始化会让该对象指向悬空,也就是指针P没有具体的指向区域,是个还没有分配指向的空指针。

  2. 实现方式2与1的区别就在于使用的对象类型是unique_ptr的派生类型,因为这个派生的指针类型构造函数不能在编译器被调用,所以链接的时候main.o在前,sing.o在后就会出现sing中对象缺省初
    初始化的时候造成了对象指针悬空。如果要正常运行,要么就是在这个构造函数前加上关键字constexpr(c11后就可以用),或者consteval(C20才能用)。要么就是在sing.cpp中的singletomInst对象定义后边就上一句init的调用。

  3. 为了防止出现第四品中出现的运因为翻译单元main.o链接在sing.o前,出现的singletonInst单例未初始化就被调用造成的静态初始化灾难。通过引入初始化类,初始化类作为单例类的子类,可以访问单例类的所有成员。然后通过初始化类的实例作为纽带,一定程度上控制初始化顺序。无论两个翻译单元连接顺序谁先谁后,因unique_ptr和init实例在调用singletonInst前边,就避免了静态实例初始化灾难

  4. 在C++中,一个翻译单元的变量定义顺序有规定,但是不同的翻译单元的顺序没有规定。第四品的问题是可能会出现两个翻译单元链接顺序先后问题造成的静态实例灾难。在本次实现1中(编译器实现unique_ptr对象的编译期初始化的话),就不在依赖两个翻译单元的链接顺序。

  5. main.cpp中 初始化类的定义 限定为static是因为在大项目中,可能会出现其他cpp文件中也用到同样名字,在链接的时候就会出错

  6. init是sing类的子类,完整的继承sing类的所有函数,所以在sing.cpp的init函数构造函数中使用new Sing的时候可以调用私有构造函数。

  • 缺点:
    这种实现方式存在未定义的问题,即便是实现1,也会因为编译器的不同出现问题,如果某个编译器没有让constexpr修饰的unique_ptr对象的构造函数在编译期被调用,完成对象的编译期初始化,那么也会出现问题。

下一品的设计思考

下一品将解决这种因为链接顺序造成的未定义问题。

相关文章:

单例九品--第五品

单例九品--第五品 上一品引入写在前边代码部分1代码部分2实现方式评注与思考下一品的设计思考 上一品引入 第四品中可能会因为翻译单元的链接先后顺序&#xff0c;造成静态初始化灾难的问题。造成的原因是因为存在调用单例对象前没有完成定义的问题&#xff0c;这一品将着重解…...

Lwip之TCP服务端示例记录(1对多)

前言 实现多个客户端同时连接初步代码结构已经实现完成(通过轮训的方式) // // Created by shchl on 2024/3/8. // #if 1#include <string.h> #include "lwip/api.h" #include "FreeRTOS.h" #include "task.h" #include "usart.h&…...

哲理:为什么你要学习编程这项技能

有一家饭店的大厨&#xff0c;烧得一手好菜&#xff0c;经过口碑相传&#xff0c;客人从五湖四海闻名而来。然而这对饭店的老板来说&#xff0c;并不单纯是一个好消息。因为客人不是奔着饭店&#xff0c;而是奔着大厨的手艺来的。老板必须想办法留住这位大厨&#xff0c;否则他…...

【机器学习300问】30、准确率的局限性在哪里?

一、什么是准确率&#xff1f; 在解答这个问题之前&#xff0c;我们首先得先回顾一下准确率的定义&#xff0c;准确率是机器学习分类问题中一个很直观的指标&#xff0c;它告诉我们模型正确预测的比例&#xff0c;即 还是用我最喜欢的方式&#xff0c;举例子来解释一下&#xf…...

融资项目——网关微服务

1. 网关的路由转发功能 在前后端分离的项目中&#xff0c;网关服务可以将前端的相关请求转发到相应的后端微服务中。 2. 网关微服务的配置 首先需要创建一个网关微服务&#xff0c;并添加依赖。 <!-- 网关 --><dependency><groupId>org.springframework.cl…...

飞驰云联CEO朱旭光荣获“科技领军人才”称号

2024年2月29日&#xff0c;苏州工业园区“优化营商环境暨作风效能建设大会”成功举办&#xff0c;会上公布了2023年度苏州工业园区第十七届第一批金鸡湖科技领军人才名单&#xff0c;Ftrans飞驰云联创始人兼CEO朱旭光先生凭借在数据安全以及文件交换领域取得的突出成果&#xf…...

Dockerfile的使用,怎样制作镜像

Docker 提供了一种更便捷的方式&#xff0c;叫作 Dockerfile docker build命令用于根据给定的Dockerfile构建Docker镜像。 docker build命令参数&#xff1a; --build-arg&#xff0c;设置构建时的变量 --no-cache&#xff0c;默认false。设置该选项&#xff0c;将不使用Build …...

外包干了5天,技术退步明显。。。。。

在湖南的一个安静角落&#xff0c;我&#xff0c;一个普通的大专生&#xff0c;开始了我的软件测试之旅。四年的外包生涯&#xff0c;让我在舒适区里逐渐失去了锐气&#xff0c;技术停滞不前&#xff0c;仿佛被时间遗忘。然而&#xff0c;生活的转机总是在不经意间降临。 与女…...

leetcode2834--找出美丽数组的最小和

1. 题意 求一个序列和。序列 a a a满足&#xff1a; 大小为 n n n ∀ 0 ≤ i , j < n , i ≠ j , a i a j ≠ t a r g e t \forall 0\le i,j \lt n,i \ne j,a_ia_j \ne target ∀0≤i,j<n,ij,ai​aj​target 找出美丽数组的最小和 2. 题解 贪心的构造这个序列。…...

【NR 定位】3GPP NR Positioning 5G定位标准解读(七)- GNSS定位方法

前言 3GPP NR Positioning 5G定位标准&#xff1a;3GPP TS 38.305 V18 3GPP 标准网址&#xff1a;Directory Listing /ftp/ 【NR 定位】3GPP NR Positioning 5G定位标准解读&#xff08;一&#xff09;-CSDN博客 【NR 定位】3GPP NR Positioning 5G定位标准解读&#xff08;…...

结构体和malloc学习笔记

结构体学习&#xff1a; 为什么会出现结构体&#xff1a; 为了表示一些复杂的数据&#xff0c;而普通的基本类型变量无法满足要求&#xff1b; 定义&#xff1a; 结构体是用户根据实际需要自己定义的符合数类型&#xff1b; 如何使用结构体&#xff1a; //定义结构体 struc…...

Nginx常用命令总结及常见问题排查

连续更新挑战第4天… 目录 常用启停命令Nginx 常见问题Nginx 如何忽略非标准http头检测?Nginx websocket代理Nginx 临时缓存不够导致下载文件失败Nginx 没有临时缓存目录权限导致下载文件失败Nginx非root用户启动无法使用80端口或者报无权限异常路由重写怎么配置?nginx 根据…...

微服务超大Excel文件导出方案优化

1、在导出Excel时经常会碰到文件过大&#xff0c;导出特别慢 2、微服务限制了请求超时时间&#xff0c;文件过大情况必然超时 优化思路&#xff1a; 1、文件过大时通过文件拆分、打包压缩zip&#xff0c;然后上传到oss,并设置有效期&#xff08;30天过期&#xff09; 2、把…...

论文阅读之Multimodal Chain-of-Thought Reasoning in Language Models

文章目录 简介摘要引言多模态思维链推理的挑战多模态CoT框架多模态CoT模型架构细节编码模块融合模块解码模块 实验结果总结 简介 本文主要对2023一篇论文《Multimodal Chain-of-Thought Reasoning in Language Models》主要内容进行介绍。 摘要 大型语言模型&#xff08;LLM…...

灯塔:CSS笔记(2)

一 选择器进阶 后代选择器&#xff1a;空格 作用&#xff1a;根据HTML标签的嵌套关系&#xff0c;&#xff0c;选择父元素 后代中满足条件的元素 选择器语法&#xff1a;选择器1 选择器2{ css } 结果&#xff1a; *在选择器1所找到标签的后代&#xff08;儿子 孙子 重孙子…...

基于Springboot的志愿服务管理系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的志愿服务管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构…...

保姆级讲解 Stable Diffusion

目录 本文讲解思路介绍 一、引入 二、Diffusion Model 三、原文的摘要和简介 四、Stable Diffusion 4.1、组成模块 4.2、感知压缩 4.3、条件控制 五、图解 Stable Diffusion 5.1、潜在空间的扩散 5.2、条件控制 5.3、采样 5.4、Diffusion Model 与 Stable Diffusion …...

HTML二识

图片&#xff0c;音频&#xff0c;视频标签 标签描述<img>定义图片<audio>定义音频<video>定义视频 定义图片&#xff1a; src&#xff1a;规定显示图片的URL&#xff08;统一资源定位符&#xff09;height&#xff1a;定义图像的高度 单位&#xff1a;px…...

[BUUCTF]-PWN:starctf_2019_babyshell解析(汇编\x00开头绕过+shellcode)

查看保护 查看ida 这里就是要输入shellcode&#xff0c;但是函数会有检测。 在shellcode前面构造一个以\x00机器码开头的汇编指令&#xff0c;这样就可以绕过函数检查了。 完整exp&#xff1a; from pwn import* context(log_leveldebug,archamd64) pprocess(./babyshell)she…...

uniapp 手写 简易 时间轴 组件

一、案例如图 该案例设计条件&#xff1a; 左侧时间 和竖线、点、内容都是居中对其的&#xff0c;上下时间点中间要有一段距离 二、编写逻辑 1. 布局结构&#xff1a;一共三个元素&#xff0c;左侧是时间和黑点&#xff0c;中间是线条&#xff0c;右侧是内容 2. 样式难点&#…...

SkyWalking 10.2.0 SWCK 配置过程

SkyWalking 10.2.0 & SWCK 配置过程 skywalking oap-server & ui 使用Docker安装在K8S集群以外&#xff0c;K8S集群中的微服务使用initContainer按命名空间将skywalking-java-agent注入到业务容器中。 SWCK有整套的解决方案&#xff0c;全安装在K8S群集中。 具体可参…...

Java 语言特性(面试系列1)

一、面向对象编程 1. 封装&#xff08;Encapsulation&#xff09; 定义&#xff1a;将数据&#xff08;属性&#xff09;和操作数据的方法绑定在一起&#xff0c;通过访问控制符&#xff08;private、protected、public&#xff09;隐藏内部实现细节。示例&#xff1a; public …...

ssc377d修改flash分区大小

1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...

Leetcode 3577. Count the Number of Computer Unlocking Permutations

Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接&#xff1a;3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯&#xff0c;要想要能够将所有的电脑解锁&#x…...

STM32标准库-DMA直接存储器存取

文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA&#xff08;Direct Memory Access&#xff09;直接存储器存取 DMA可以提供外设…...

【CSS position 属性】static、relative、fixed、absolute 、sticky详细介绍,多层嵌套定位示例

文章目录 ★ position 的五种类型及基本用法 ★ 一、position 属性概述 二、position 的五种类型详解(初学者版) 1. static(默认值) 2. relative(相对定位) 3. absolute(绝对定位) 4. fixed(固定定位) 5. sticky(粘性定位) 三、定位元素的层级关系(z-i…...

[10-3]软件I2C读写MPU6050 江协科技学习笔记(16个知识点)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作

一、上下文切换 即使单核CPU也可以进行多线程执行代码&#xff0c;CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短&#xff0c;所以CPU会不断地切换线程执行&#xff0c;从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...

在鸿蒙HarmonyOS 5中使用DevEco Studio实现录音机应用

1. 项目配置与权限设置 1.1 配置module.json5 {"module": {"requestPermissions": [{"name": "ohos.permission.MICROPHONE","reason": "录音需要麦克风权限"},{"name": "ohos.permission.WRITE…...

【Java学习笔记】BigInteger 和 BigDecimal 类

BigInteger 和 BigDecimal 类 二者共有的常见方法 方法功能add加subtract减multiply乘divide除 注意点&#xff1a;传参类型必须是类对象 一、BigInteger 1. 作用&#xff1a;适合保存比较大的整型数 2. 使用说明 创建BigInteger对象 传入字符串 3. 代码示例 import j…...