单例九品--第五品

单例九品--第五品

  • 上一品引入
  • 写在前边
  • 代码部分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对象的构造函数在编译期被调用,完成对象的编译期初始化,那么也会出现问题。

下一品的设计思考

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

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

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

相关文章

力扣日记3.8-【回溯算法篇】37. 解数独

力扣日记&#xff1a;【回溯算法篇】37. 解数独 日期&#xff1a;2023.3.8 参考&#xff1a;代码随想录、力扣 37. 解数独 题目描述 难度&#xff1a;困难 编写一个程序&#xff0c;通过填充空格来解决数独问题。 数独的解法需 遵循如下规则&#xff1a; 数字 1-9 在每一行只…

Python 初步了解urllib库:网络请求的利器

目录 urllib库简介 request模块 parse模块 error模块 response模块 读取响应内容 获取响应状态码 获取响应头部信息 处理重定向 关闭响应 总结 在Python的众多库中&#xff0c;urllib库是一个专门用于处理网络请求的强大工具。urllib库提供了多种方法来打开和读取UR…

ViewModel

1.ViewModel的诞生与作用 Activity package com.tiger.viewmodel;import androidx.appcompat.app.AppCompatActivity; import androidx.lifecycle.ViewModelProvider;import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Te…

[BUG] docker运行Java程序时配置代理-Dhttp.proxyHost后启动报错

[BUG] docker运行Java程序时配置代理-Dhttp.proxyHost后启动报错 bug现象描述 版本&#xff1a;2.0.4&#xff08;客户端和服务端都是&#xff09; 环境&#xff1a;私有云环境&#xff0c;只有少量跳板机器可以访问公网&#xff0c;其他机器均通过配置代理方式访问公网 bug现…

Transformer中的 Add Norm

Transformer中的 Add & Norm flyfish Add 同一个意思 Residual connections&#xff0c;Skip Connections Norm 包括Post layer normalization和Pre layer normalization Post layer normalization&#xff1a;Transformer 论文中使用的方式&#xff0c;将 Layer norm…

鸿蒙Harmony应用开发—ArkTS声明式开发(基础手势:ImageSpan)

Text组件的子组件&#xff0c;用于显示行内图片。 说明&#xff1a; 该组件从API Version 10开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 子组件 无 接口 ImageSpan(value: ResourceStr | PixelMap) 参数&#xff1a; 参数名参数类…

前端实现生成图片并批量下载,下载成果物是zip包

简介 项目上有个需求&#xff0c;需要根据表单填写一些信息&#xff0c;来生成定制的二维码图片&#xff0c;并且支持批量下载二维码图片。 之前的实现方式是直接后端生成二维码图片&#xff0c;点击下载时后端直接返回一个zip包即可。但是项目经理说后端实现方式每次改个东西…

【kubernetes】关于k8s集群中的ingress规则案例

目录 一、k8s 对外服务之 Ingress 1.1什么是ingress 1.2外部的应用能够访问集群内的服务有哪些方案&#xff1f; 1.3Ingress 组成 1.4Ingress-Nginx 工作原理 1.5ingress 暴露服务的方式 二、实操ingress暴露服务 前期.部署 nginx-ingress-controller 2.1基于host网络…

JAVA后端开发面试基础知识(八)——Spring

Spring 1. 什么是 Spring 框架 Spring是一个轻量级Java开发框架 我们一般说 Spring 框架指的都是 Spring Framework&#xff0c;它是很多模块的集合&#xff0c;使用这些模块可以很方便地协助我们进行开发&#xff0c;比如说 Spring 支持 IoC&#xff08;Inverse of Control:控…

BUUCTF-Misc6

数据包中的线索1 1.打开附件 发现是一个流量包 2.Wireshark 用Wireshark打开 右键属性&#xff0c;追踪tcp流&#xff0c;发现base64编码 3.base64转图片 将base64编码保存为文本文档 Python脚本 import os,base64 with open("/root/桌面/3/1.txt","r"…

文章解读与仿真程序复现思路——电网技术EI\CSCD\北大核心《考虑多能互补灵活性和用户低碳意愿的区域综合能源系统鲁棒优化调度》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 论文与完整源程序_电网论文源程序的博客-CSDN博客https://blog.csdn.net/liang674027206/category_12531414.html 电网论文源程序-CSDN博客电网论文源…

记录汇川:IO隔离编程

IO隔离&#xff1a;方便程序修改 无论是输入点坏了还是输出点坏了&#xff0c;或者人为接错线&#xff0c;或者对调点&#xff0c;我们只需要更改IO隔离得输入输出就可以了。方便。 停止按钮外接常闭&#xff0c;里面也使用常闭&#xff0c;为了断线检测功能(安全)&#xff…