自己做的网站点击赚钱,免费制作二级网站,网站建设 技术 哪些,建设施工网络平台单例九品第七品 上一品引入写在前边代码部分实现思路的评注与思考下一品的设计思考 上一品引入
第六品着重解决了因为链接顺序造成的未定义问题#xff0c;通过强制对象完成编译期初始化和使用基本类型代替抽象类型#xff0c;使得全局对象的缺省初始化从不平凡变为平凡初始… 单例九品第七品 上一品引入写在前边代码部分实现思路的评注与思考下一品的设计思考 上一品引入
第六品着重解决了因为链接顺序造成的未定义问题通过强制对象完成编译期初始化和使用基本类型代替抽象类型使得全局对象的缺省初始化从不平凡变为平凡初始化从而解决了因翻译单元链接顺序不同造成的未定义问题。但是因为初始化子类中的全局对象构造需要计数值的配合正是计数值这种写法造成了多线程的安全问题第六品的例子3可能会出现单例对象的过早销毁从而因此程序崩溃的问题。 所以第七品将会着重解决这个问题。
另外从第五品开始引入了指针全局对象都变成了指针类型第五品是智能指针和智能指针的拓展类第六品为裸指针。指针与引用的区别就是引用完成了对象的绑定就不会在改变绑定对象就可以理解为是一个有固定指向的指针。但是指针的话如果没有限定指向是可以自由变更朝向的这个问题就是第五品以后将全局对象变为指针后引入的问题。但是这个问题不在本品中进行讨论指针类型的全局对象存在被修改的问题将在第八品中进行讨论。第七品只讨论第六品引入的线程安全问题。
写在前边
基本思路 • 为引用计数引入线程安全操作优点 • 全局对象初始化/销毁多线程安全缺点 • 指针有被修改的风险
代码部分
三个文件: sing.cpp main.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 std::endl;std::cout singletonInst-val std::endl;
}sing.cpp
#include sing.h
#include memory
#include iostreamSing* singletonInst; // 全局对象但是是指针类型的对象在main函数中可能会被修改造成程序崩溃Sing::Init::Init()
{auto count Sing::Init::RefCount();。auto ori count.fetch_add(1);//返回的ori为count加1前的值if (ori 0){singletonInst new Sing(); // 全局对象访问点}
}Sing::Init::~Init()
{auto count Sing::Init::RefCount();auto ori count.fetch_sub(1);//返回的ori为count减1前的值if (ori 1){delete singletonInst;singletonInst nullptr;}
}sing.h
#pragma once
#include atomic
#include iostreamclass Sing
{
public:struct Init{Init();Init(const Init) delete;Init operator (const Init) delete;~Init();static std::atomicunsigned RefCount(){static std::atomicunsigned singletonCount{ 0 };return singletonCount;}};private:Sing(){std::cout Sing construct\n;val 100;}~Sing(){std::cout Sing destroy\n;}Sing(const Sing) delete;Sing operator (const Sing) delete;
public:int val;
};extern Sing* singletonInst;output g -c main.cpp g -c sing.cpp g main.o sing.o -o ./ms g sing.o main.o -o ./sm 两种链接顺序的结果都是
Sing construct
get value: 100
0x557949424eb0
100
Sing destroy实现思路的评注与思考 这种实现方式使用静态初始化函数的方式完成std::atomic类型的引用计数值的构建使用静态初始化函数的方式完成计数值singletonCount的初始化。 为什么使用std::atmoic完成singletonCount的定义 答: 1) std::amotic是个原子操作用于实现原子操作。原子操作是在并发编程中用来确保多个线程在同一时间对共享数据的访问是安全的不会发生竞态条件race condition的操作。2) 在多线程编程中如果多个线程同时访问同一块内存区域并且其中至少有一个线程对该内存区域进行写操作就可能导致竞态条件。std::atomic 提供了一种机制来避免竞态条件能够确保在单个原子操作中对共享变量进行读取、修改、写入等操作从而保证这些操作的完整性。 使用静态初始化函数的方式完成计数值的初始化同样会引入多线程安全的时间损失。如果在main函数中多个线程都调用RefCount函数(如果是第一次就构建singletonCount对象。随后每次掉用这个函数都不会构建新的这是静态初始化的作用)那么每次都需要加锁判断解锁操作 为什么使用sing类中的静态函数完成计数值singletonCount的定义在之后每一次使用计数值的时候再去调用接口Recount呐这样也会引起多线程的时间消耗就相当于构建了一个计数值到单例。这么设计也是从程序安全性和限制用户构建sing类的多个不同对象来设计的。具体怎么解释还没想好。待后续补充
缺点 全局对象是指针类型的对象在main函数中可能会被修改造成程序崩溃。全局对象改为指针类型是在第四品的例子2到第五品的转换为了解决第四品的例子2中因为翻译单元链接顺序导致的静态实例初始化灾难从而在第五品中引入了指针类型的全局对象并在初始化的时候控制对象的初始化从而避免链接问题造成的未定义问题 [singletonInst.reset( new Sing)]。为了方便修改指针的指向
下一品的设计思考
第八品将解决全局对象为指针而引入的指针会被修改的风险