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

c++特殊类设计(重点单例模式)

一、设计一个不被拷贝的类

c++98:只声明拷贝构造和赋值重载,定义私有。

c++11:拷贝构造和赋值重载 = delete 禁止调用

二、只能在堆上创建对象

1、思路

(1)构造函数私有(禁止随意创建对象)拷贝构造和赋值重载 = delete(创建的对象是在栈上)

(2)定义静态公有函数new对象(因为一开始没有对象创建,无法调用成员函数,所以定义成静态函数,用类域调用)

2、代码实现

class HeapOnly
{
public:static HeapOnly* GetObj(){return new HeapOnly;}private:HeapOnly() {}HeapOnly(const HeapOnly&) = delete;HeapOnly& operator=(const HeapOnly&) = delete;
}

三、只能在栈上创建对象

1、思路

(1)构造函数私有(禁止随便new对象)

(2)定义静态公有函数返回构造函数创建的对象(因为一开始没有对象创建,无法调用成员函数,所以定义成静态函数,用类域调用)

(3)operator new 和 operator delete = delete(禁止new对象)

2、代码实现

class StackOnly
{
public:StackOnly GetObj(){return StackOnly();}void* operator new(sizt_t size) = delete;void operator delete(void* p) = delete;
private:StackOnly() {}
}

四、设计一个不能被继承的类

c++98:构造函数私有化

c++11:类后面加 final

五、单例模式(重点)

1、单例模式介绍

目的:全局只有一个对象。

原则:所以可以创建静态对象实现,不能在 main 函数里面创建,不能在成员函数里面创建,只能在全局创建。

实现方式:饿汉模式、懒汉模式

2、饿汉模式介绍

(1)理论

在 main 函数之前就创建了一个静态对象。

(2)思路

限制对象个数,构造函数私有,拷贝构造和赋值重载 = delete

类内只声明静态对象,此对象只属于类,不属于任何对象,所以类外定义静态对象

定义静态公有函数返回对象

(3)代码实现

class ConfigInfo
{
public:static ConfigInfo* GetInstance(){return &_sInfo;}private:ConfigInfo() {}ConfigInfo(const ConfigInfo&) = delete;ConfigInfo& operator=(const ConfigInfo&) = delete;private:// 声明 static ConfigInfo _sInfo;
};// 定义
ConfigInfo ConfigInfo::_sInfo;

(4)饿汉模式问题

a、饿汉模式不管有没有用到该对象在 main 函数开始前都已经创建好了,如果很多单例模式是饿汉模式,对象初始化资源又很多,势必导致迟迟不会进入 main 影响效率。

b、两个单例类如果有依赖关系,饿汉模式无法解决。

(5)饿汉模式创建对象过程

main() 函数前也会有代码要执行。编译期的时候,给 _sInfo 分配内存,运行期的时候,在main() 函数前就运行了一些指令,初始化静态对象 _sInfo ,执行了 _sInfo 的构造函数(因为类外初始化的代码,初始化就是在构造对象且赋值),所以到执行 GetInstance() 获取该实例前,对象就已经构造好了,这个就是饿汉式单例。
因为在main() 函数前就已经有这个实例了,那肯定也是多线程安全的,因为这个时候也没创建多个线程,并不会重复构造实例。

3、懒汉模式介绍

(1)理论

在第一次调用创建函数时创建对象,之后已经存在对象就不会创建。

(2)思路

限制对象个数,构造函数私有,拷贝构造和赋值重载 = delete

c++11:直接在创建函数中创建静态局部对象(之后重点讲),不在其他任何地方进行声明定义。

c++98:类内定义对象指针,类外初始化成 nullptr ,创建函数中再进行赋值。

定义静态公有函数返回对象。

(3)代码实现(c++11 静态局部对象)

class ConfigInfo
{
public:static ConfigInfo* GetInstance(){	static ConfigInfo info;return &info;}private:ConfigInfo() {}ConfigInfo(const ConfigInfo&) = delete;ConfigInfo& operator=(const ConfigInfo&) = delete;
};

(4)代码实现(c++98 对象指针)

class ConfigInfo
{
public:static ConfigInfo* GetInstance(){// 双检查加锁// t1 t2if (_spInfo == nullptr)      // 性能{unique_lock<mutex> lock(_mtx);if (_spInfo == nullptr)  // 线程安全{_spInfo = new ConfigInfo;}}return _spInfo;}private:ConfigInfo() {}ConfigInfo(const ConfigInfo&) = delete;ConfigInfo& operator=(const ConfigInfo&) = delete;private:static ConfigInfo* _spInfo;static mutex _mtx;
};ConfigInfo* ConfigInfo::_spInfo = nullptr;
mutex ConfigInfo::_mtx;

除去考虑多线程安全加锁的步骤,和饿汉模式很类似,只不过在类外定义时不是有效的,只有第一次调用 GetInstance() 函数才是真正创建了对象。

(5)多线程时的安全问题

c++11

由于静态局部变量在初始化时,在汇编指令上已经自动添加线程互斥指令了。所以如果线程A调用该函数,在初始化未完成之前,线程B不会执行该初始化操作。线程A完成初始化后,已经初始化过的变量,其它线程不会再重复进行初始化操作,从而只有一个实例对象产生。

c++98

static ConfigInfo* GetInstance()
{if (_spInfo == nullptr) {_spInfo = new ConfigInfo;}return _spInfo;
}

一开始没有锁,在多线程情况下是不安全的,多线程进入会导致多次创建对象。

所以该函数不是可重入函数(能够被多个线程同时调用的函数,并且能保证函数结果正确,不必担心数据错误的函数)

static ConfigInfo* GetInstance()
{unique_lock<mutex> lock(_mtx);if (_spInfo == nullptr)  // 线程安全{_spInfo = new ConfigInfo;}return _spInfo;
}

这次我们加上锁已经解决了多线程安全问题,但是会发现只有第一次创建对象时要保证线程安全,其他时候由于对象只会有一个即使函数被重入依然只有一个对象。

而上面的代码每一次都会把进入函数的线程堵在锁外面等解锁,即使对象已经被创建了,进入锁也没有意义,这样效率会有损耗。

static ConfigInfo* GetInstance()
{if (_spInfo == nullptr)      // 性能{unique_lock<mutex> lock(_mtx);if (_spInfo == nullptr)  // 线程安全{_spInfo = new ConfigInfo;}}return _spInfo;
}

锁 + 双重判断就可以解决上面所有问题。

(6)深入了解静态局部对象

静态局部变量也是在全局区,和静态全局变量,全局变量一样,是在编译器就被分配了内存。

但是静态局部变量的初始化是运行到该语句时,进行初始化。

c语言是编译时分配内存和初始化的。

c++是编译时分配内存,运行时首次使用时初始化。

主要是由于c++引入对象后,要进行初始化必须执行相应构造函数和析构函数,在构造函数或析构函数中经常会需要进行某些程序中需要进行的特定操作,并非简单地分配内存。所以c++标准定为全局或静态对象是有首次用到时才会进行构造。

实际上对于这种自定义类型来说,无论全局静态变量还是局部静态变量,都是执行动态初始化,也就是都得在代码真正执行时,要调用了其构造函数才能初始化(C没有对象的概念,所以C都是编译期初始化)
简单点就是静态对象在编译时分配内存且值赋为0或null,运行时构造对象并赋值

也就是实际上分两个阶:第一是编译时的零初始化(分配内存),第二是运行时的动态初始化

其实只要知道静态和动态就好了。静态就是不需要运行程序,在运行前就能放好。动态初始化就得执行程序。

所以c++11懒汉模式原理:局部静态变量只会初始化一次的特性 + 汇编指令自动添加的锁

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Mysql客户端连接是报错:1045-Access denied for user
  • 方法的定义
  • Nuxt 入门实战 - 05:特性
  • vue项目中scss文件导出,js文件引入scss文件时为空{}
  • 代码随想录 -- 哈希表 -- 三数之和
  • Django 框架中F和Q的作用
  • 富格林:极力破除欺诈维护安全
  • 以太网PHY驱动调试笔记(KSZ8081)
  • 代码随想录算法day25 | 贪心算法part03 | 134. 加油站,135. 分发糖果,860.柠檬水找零,406.根据身高重建队列
  • 算法学习-基础数据结构
  • 【python】懂车帝字体反爬逐层解密案例(附完整代码)
  • 中秋佳节,数码好礼伴团圆:中秋节五大数码礼品指南
  • docker compose用法详解
  • 深度确定问题中的树森林操作:分析与实现
  • OpenCV+Python识别机读卡
  • 【5+】跨webview多页面 触发事件(二)
  • 【跃迁之路】【733天】程序员高效学习方法论探索系列(实验阶段490-2019.2.23)...
  • ABAP的include关键字,Java的import, C的include和C4C ABSL 的import比较
  • exports和module.exports
  • httpie使用详解
  • Java小白进阶笔记(3)-初级面向对象
  • Js基础知识(一) - 变量
  • js数组之filter
  • Python十分钟制作属于你自己的个性logo
  • 从零到一:用Phaser.js写意地开发小游戏(Chapter 3 - 加载游戏资源)
  • 前端攻城师
  • 前端之Sass/Scss实战笔记
  • 如何借助 NoSQL 提高 JPA 应用性能
  • 通信类
  • 正则表达式
  • 最简单的无缝轮播
  • puppet连载22:define用法
  • 继 XDL 之后,阿里妈妈开源大规模分布式图表征学习框架 Euler ...
  • ​批处理文件中的errorlevel用法
  • # Java NIO(一)FileChannel
  • #HarmonyOS:Web组件的使用
  • #微信小程序:微信小程序常见的配置传旨
  • $LayoutParams cannot be cast to android.widget.RelativeLayout$LayoutParams
  • (C语言)输入自定义个数的整数,打印出最大值和最小值
  • (SERIES12)DM性能优化
  • (附源码)ssm捐赠救助系统 毕业设计 060945
  • (附源码)ssm考生评分系统 毕业设计 071114
  • (四)JPA - JQPL 实现增删改查
  • .bat批处理(六):替换字符串中匹配的子串
  • .NetCore项目nginx发布
  • .Net调用Java编写的WebServices返回值为Null的解决方法(SoapUI工具测试有返回值)
  • .Net下使用 Geb.Video.FFMPEG 操作视频文件
  • @Repository 注解
  • @RequestMapping-占位符映射
  • [<MySQL优化总结>]
  • [20171101]rman to destination.txt
  • [20190113]四校联考
  • [④ADRV902x]: Digital Filter Configuration(发射端)
  • [bzoj2957]楼房重建
  • [C++]二叉搜索树