C++中特殊类设计/单例模式

news2024/11/17 5:01:05

特殊类设计

​ 本篇将会介绍一些特殊类的设计:不能拷贝的类、只能在堆上创建的类、只能在栈上创建的对象、不能被继承的类、只能创建一个对象的类(单例模式)。

文章目录

      • 特殊类设计
        • 不能被拷贝的类
        • 只能在堆上创建对象的类
        • 只能在栈上创建对象的类
        • 不能被继承
        • 单例模式(只可以创建一个对象)
          • 饿汉模式
          • 懒汉模式

不能被拷贝的类

​ 拷贝只会发生在两个场景中:拷贝构造函数和赋值运算符重载函数,因此想要让一个类禁止老贝,只需要让该类不能调用拷贝构造函数以及赋值运算符重载。如下:

C++98写法:

​ 在C++98中还没有delete关键字,所以只能把拷贝构造和赋值运算符重载给放在private域中,这样类外对象就访问不到这两个对象,也就不能进行拷贝。

class CopyBan {
public:
    // ...
private:
    CopyBan(const CopyBan&);
    CopyBan& operator=(const CopyBan&);
    // ...
};

C++11之后写法:

​ C++11可以直接使用delete关键字将这两个函数删除掉,如下:

class CopyBan {
public:
    // ...
    CopyBan(const CopyBan&) = delete;
    CopyBan& operator=(const CopyBan&) = delete;
};
只能在堆上创建对象的类

​ 只能在堆上创建对象,也就意味着我们不能在类外直接声明定义,所以我们需要将构造函数私有化(不能禁止,还需要创建),同时还需要将拷贝构造函数禁止掉(或者私有化),因为可能通过拷贝构造在栈上创建对象。

​ 既然不能通过在类外定义,那么我们就需要在类内提供一个静态成员函数用于申请堆上的对象,如下:

class HeapOnly {
public:
    static HeapOnly* CreateObj() {
        return new HeapOnly;
    }

    // ...
    ~HeapOnly() {
        // ...
        delete this;
    }
private:
    HeapOnly() {}

    HeapOnly(const HeapOnly&) {}
    // HeapOnly(const HeapOnly&) = delete;
    // ...
};

​ 同时还需要在析构函数中将自己给删除掉。

只能在栈上创建对象的类

​ 既然是只能在栈上创建的对象,那么我们就应该禁掉new和delete,但是new和delete是两个全局的关键字,我们可以在类内将new和delete重载,然后使用delete删除掉,这样对象不会被new出来了,如下:

// 第一种写法
class StackOnly {
public:
    void* operator new(size_t size) = delete;
    void operator delete(void* p) = delete;
    // ...
};

// 第二种写法
class StackOnly {
public:
    static StackOnly CreateObj() {
        return StackOnly();
    }

    void* operator new(size_t size) = delete;
    void operator delete(void* p) = delete;
    // ...
private:
    StackOnly() {}
};
不能被继承

​ 只需要将类的构造函数给私有化,子类调用不到基类的构造函数,就不可以继承基类,如下:

class NonIherit {
public:
    static NonIherit GetInstance() {
        return NonIherit();
    }
private:
    NonIherit() {}
};

// 也可以使用C++11中的关键字final
class NonIherit final {
    // ...
}
单例模式(只可以创建一个对象)

​ 一个类只能创建一个对象,这就是单例模式。该模式可以保证系统中该类中只有一个实例,并提供一个访问它的全局访问点。该实例被所有程序模块共享。比如在某个服务器程序中,将该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过单例对象来获取这些配置信息,这种方式简化了在复杂环境下的配置管理。

​ 单例模式一共有两种设计模式:

饿汉模式

​ 饿汉模式下的单例对象,在程序启动时就创建(进入main函数前),不管将来是否会使用到该单例对象,都会创建出来。如下:

class ConfigInfo {
public:
    static ConfigInfo* GetInstance() {
        return &_sinfo;
    }
	// 删除拷贝构造和赋值重载
    ConfigInfo(const ConfigInfo&) = delete;
    ConfigInfo& operator=(const ConfigInfo&) = delete;
	// ...
private:
    // 构造函数私有化
    ConfigInfo() {}

    std::string _ip;
    uint16_t _port;
    // ...

    // 静态变量,在类内声明
    static ConfigInfo _sinfo;
};

// 类外定义
ConfigInfo ConfigInfo::_sinfo;

​ 如上所示,我们先将构造函数私有化,同时删除掉拷贝构造和赋值重载函数,接着在类内声明静态对象,在类外进行定义,这个时候就创建出了全局唯一的一个对象,我们可以使用GetInstance进行该对象访问。

​ 对于饿汉模式的优缺点如下:

优点:

​ 实现较为简单。

缺点:

​ 当单例对象较多的时候,会导致进程启动慢,因为各种单例对象在初始化的时候可能需要加载较多的资源。

​ 同时单例对象之间若存在互相依赖关系,将进一步导致效率降低,因为单例对象初始化的顺序不固定。

懒汉模式

​ 懒汉模式就是只有在需要使用该单例对象的时候才会加载该单例对象的资源,也就是一种延迟加载。实现如下:

class ConfigInfo {
public:
    // static ConfigInfo* GetInstance() {
    //     // 这种方式在C++11之前,多线程调用会存在线程安全问题
    //     // 可能会创建出多个单例对象
    //     // C++11对该问题进行了特殊处理
    //     static ConfigInfo info;
    //     return &info;
    // }

    static ConfigInfo* GetInstance() {
        if (_spinfo == nullptr) {
            // 判断两次_spinfo是否为nullptr是因为我们只需呀对
            // _spinfo变量初始化一次,也就是上锁初始化一次
            // 假若只判断_spinfo是否为nullptr,则每次都要加锁
            // 较为浪费效率
            std::unique_lock<std::mutex> lock(_mtx);
            if (_spinfo == nullptr) 
                _spinfo = new ConfigInfo;
        }
        return _spinfo;
    }

    // 删除拷贝构造和赋值重载
    ConfigInfo(const ConfigInfo&) = delete;
    ConfigInfo& operator=(const ConfigInfo&) = delete;
    // ...

    void SetIp(const std::string& ip) {
        _ip = ip;
    }

    std::string GetIp() {
        return _ip;
    }
private:
    // 构造函数私有化
    ConfigInfo() {}

    std::string _ip;
    uint16_t _port;
    // ...

    static std::mutex _mtx;
    static ConfigInfo* _spinfo;
};

std::mutex ConfigInfo::_mtx;
ConfigInfo* ConfigInfo::_spinfo = nullptr;

​ 如上所示,实现懒汉单例模式一共存在两种方式:

第一种:

​ 直接在GetInstance函数内定义一个静态的对象,每一次返回即可。这种方式实现得最为简单,不过这种方式在C++11之前会存在线程安全问题。C++11对这样的单例模式进行了特殊处理。

第二种:

​ 定义静态对象指针,以及锁,在类外定义该类和对象指针(两个变量基本没有消耗啥资源),然后在GetInstance函数中判断对象静态指针是否被初始化,没有初始化则new一个对象,已经初始化则直接返回即可。

​ 优缺点:

​ 优点:第一次使用实力对象的时候才创建对象,进程启动无负载。多个单例对象实力启动顺序可以控制。

​ 缺点:实现较为复杂。

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

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

相关文章

CentOS 8 安装 chronyd 服务

操作场景 目前原生 CentOS 8 不支持安装 ntp 服务&#xff0c;因此会发生时间不准的问题&#xff0c;需使用 chronyd 来调整时间服务。CentOS 8以及 TencentOS 3.1及以上版本的实例都使用 chronyd 服务实现时钟同步。本文介绍了如何在 CentOS 8 操作系统的腾讯云服务器上安装并…

datawhale11月组队学习 模型压缩技术3:2:4结构稀疏化BERT模型

文章目录 一、 半结构化稀疏性简介二、 代码实践2.1 定义辅助函数2.2 加载模型、tokenizer和数据集2.3 测试baseline模型指标2.4 对BERT-base模型进行半结构稀疏化 《datawhale2411组队学习之模型压缩技术1&#xff1a;模型剪枝&#xff08;上&#xff09;》&#xff1a;介绍模…

大数据学习15之Scala集合与泛型

1. 概述 大部分编程语言都提供了数据结构对应的编程库&#xff0c;并称之为集合库(Collection Library)&#xff0c;Scala 也不例外&#xff0c;且它还拥有以下优点&#xff1a; 易用&#xff1a;灵活组合运用集合库提供的方法&#xff0c;可以解决大部分集合问题 简洁&#xf…

force stop和pm clear的区别

前言&#xff1a;因为工作中遇到force stop和pm clear进程后&#xff0c;进程不能再次挂起&#xff0c;谷歌系统共性问题&#xff0c;服务类应用经清缓存后当下服务就会挂掉&#xff0c;需要系统重启才能恢复。为了更好的“丢锅”&#xff0c;需要进一步学习force stop和pm cle…

SAP+Internet主题HTML样式选择

SAP目前只支持三种HTML样式选择&#xff1a; 样式一 背景色&#xff1a;深色&#xff0c;蓝 特点&#xff1a;适中型排列&#xff0c;与SAP界面排列相同&#xff0c;富含UI特征&#xff0c;整齐美观 URL地址&#xff1a;http://cn1000-sap-01.sc.com:8000/sap/bc/gui/sap/it…

VBA学习笔记:点击单元格显示指定的列

应用场景&#xff1a; 表格中列数较多&#xff0c;特定条件下隐藏一些无关的列&#xff0c;只保留相关的列&#xff0c;使表格更加清晰。 示例&#xff1a;原表格如下 点击一年级&#xff0c;只显示一年级相关的科目&#xff1a; 点击二年级&#xff0c;只显示二年级相关的科…

java版嘎嘎快充汽车单车充电系统源码系统jeecgboot

汽车使用云快充1.6 1.5协议&#xff0c;单车用的铁塔协议 前端uniapp、后端jeecgbootvue2

这些场景不适合用Selenium自动化!看看你踩过哪些坑?

Selenium是自动化测试中的一大主力工具&#xff0c;其强大的网页UI自动化能力&#xff0c;让测试人员可以轻松模拟用户操作并验证系统行为。然而&#xff0c;Selenium并非万能&#xff0c;尤其是在某些特定场景下&#xff0c;可能并不适合用来自动化测试。本文将介绍Selenium不…

qt QFrame详解

1、概述 QFrame是Qt框架中用于提供框架或边框的控件&#xff0c;主要用于在图形用户界面&#xff08;GUI&#xff09;中创建框架&#xff0c;并提供各种边框样式和功能。它是Qt中一个基础的容器类&#xff0c;也是许多基础控件的基类&#xff0c;可以被QLCDNumber、QToolBox、…

python json详解

json 是 Python 中用于处理 JSON 数据的标准库。JSON&#xff08;JavaScript Object Notation&#xff09;是一种轻量级的数据交换格式&#xff0c;易于人类阅读和编写&#xff0c;同时也易于机器解析和生成。Python 的 json 模块提供了将 Python 对象与 JSON 数据相互转换的功…

ISUP协议视频平台EasyCVR私有化部署视频平台如何实现RTMP推流将大疆无人机的视频画面回传?

在现代视频监控和流媒体技术领域&#xff0c;EasyCVR视频融合云平台以其卓越的性能和灵活性&#xff0c;成为了跨区域、网络化视频监控综合管理的理想选择。作为TSINGSEE青犀视频“云边端”架构体系中的核心组件&#xff0c;私有化部署视频平台EasyCVR不仅能够实现视频数据的集…

【时间之外】IT人求职和创业应知【37】-AIGC私有化

目录 新闻一&#xff1a;2024智媒体50人成都会议暨每经20周年财经媒体峰会召开 新闻二&#xff1a;全球机器学习技术大会在北京召开 新闻三&#xff1a;区块链技术在金融领域的应用取得新突破 不知不觉的坚持了1个月&#xff0c;按照心理学概念&#xff0c;还要坚持2个月&am…

SqlServer 存储临时数据

WITH 子句中的 公用表表达式&#xff08;CTE&#xff09;、临时表&#xff08;Temporary Table&#xff09; 和 表变量&#xff08;Table Variable&#xff09; 都可以存储临时数据&#xff0c;但它们的使用场景、生命周期、作用范围和性能特性有所不同。下面是这三者之间的主要…

jmeter基础05_第1个http请求

本节课使用网站“httpbin.org”进行基础的http请求全流程。 请求获取httpbin.org的首页&#xff1a; 请求方法&#xff1a;GET URL&#xff1a;http://httpbin.org 参数&#xff1a;无 1、操作步骤 ① 打开jmeter&#xff1a;命令行窗口输入“jmeter”并回车。 ② 添加线程组…

SpringBoot+MyBatis+MySQL的Point实现范围查找

前言 最近做了一个功能&#xff0c;需要通过用户当前位置点获取指定范围内的数据。由于后端存储用的是 MySQL&#xff0c;故选择使用 MySQL 中的 Point 实现范围查找功能。ORM 框架用的是 MyBatis&#xff0c;MyBatis 原生并不支持 Point 字段与 POJO 的映射&#xff0c;需要自…

计算机网络中的数据包传输机制详解

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 计算机网络中的数据包传输机制详解 计算机网络中的数据包传输机制详解 计算机网络中的数据包传输机制详解 引言 数据包的基本概念…

DAY110代码审计-PHP框架开发篇ThinkPHP版本缺陷不安全写法路由访问利用链

https://blog.csdn.net/m0_60571842/article/details/139057898 看这个原作者 知识点&#xff1a; 1、PHP框架学习-ThinkPHP-架构&调试&路由&接受2、PHP框架审计-ThinkPHP-不安全写法&版本漏洞 框架审计总结方向&#xff1a; 1、版本不安全写法怎么检测 -本…

【日志】力扣11.盛水最多的容器

2024.11.15 【力扣刷题】 11.盛水最多的容器 - 力扣&#xff08;LeetCode&#xff09; int maxArea(int* height, int heightSize) {int max 0;int V 0;int left 0;int right heightSize - 1;while (left < right) {if (height[left] > height[right]) {V height[r…

leetcode100:相同的树

给你两棵二叉树的根节点 p 和 q &#xff0c;编写一个函数来检验这两棵树是否相同。 如果两个树在结构上相同&#xff0c;并且节点具有相同的值&#xff0c;则认为它们是相同的。 示例 1&#xff1a; 输入&#xff1a;p [1,2,3], q [1,2,3] 输出&#xff1a;true示例 2&…

❤React-React 组件基础(类组件)

❤React-React 组件基础 1、组件化开发介绍 组件化开发思想&#xff1a;分而治之 React的组件按照不同的方式可以分成类组件&#xff1a; 划分方式一&#xff08;按照组件的定义方式&#xff09; 函数组件(Functional Component )和类组件(Class Component)&#xff1b; …