C++——浅拷贝、深拷贝、写时拷贝详解

2023-11-19

C++——浅拷贝、深拷贝、写时拷贝详解

浅拷贝与深拷贝

用String类模拟用 将“/0”拷贝进去:
在这里插入图片描述
调用系统默认的拷贝构造函数,结果就是内容相同,地址相同。
在这里插入图片描述
说明这个方法是浅拷贝方法。
浅拷贝方法带来的问题就是同一个空间被析构两次,程序就直接崩了。如下图所示:
在这里插入图片描述
浅拷贝就是只有s和s1两个对象同时指向一个地址空间,而析构函数需要执行两次,因而会对内存空间进行两次析构

将代码中的拷贝构造函数写完整后,程序就不崩溃了,此方法为深拷贝方法:
在这里插入图片描述
深拷贝方法就是对以前的地址空间再进行拷贝一份,使得拷贝的对象指向新的地址空间,这样就可以避免一个空间同时被析构两次的问题。
在这里插入图片描述
那么问题就是:深拷贝就一定好吗?

深拷贝最大的特点就是拷贝一个一模一样的空间,但是缺点是比较浪费空间。构造构对象越多,单独生成的空间就越多且都一样。从用户来看,数据从哪里来并不重要,然而浅拷贝会造成数据空间被释放多次。因此浅拷贝有浅拷贝的好,深拷贝有深拷贝的好!

解决浅拷贝的问题 —— 引用计数

引用空间就是给这个空间一个数代表这个空间目前有两个对象在使用,析构的时候就减少相应的次数,只要不是0就说明这个空间还有对象在用。
在这里插入图片描述
使用use_count这个变量去做(因为要对所有对象使用,所以必须是静态的static);每一个对象应该维护自己的引用计数,否则引用同一个就会导致区分不了不同的对象。

具体做法:

首先增加一个字符串引用计数器类:

// 增加字符串引用类;
// An highlighted block
class String_rep
{
	friend class String;
	friend ostream& operator<<(ostream &out, const String &s);
public:
	String_rep(const char *str = "") :use_count(0)
	{
		m_data = new char[strlen(str) + 1];
		strcpy(m_data, str);
	}
	String_rep(const String_rep &rep) :use_count(0)
	{
		m_data = new char[strlen(rep.m_data) + 1];
		strcpy(m_data, rep.m_data);
	}
	String_rep& operator=(const String_rep &rep);
	~String_rep()
	{
		delete[]m_data;
		m_data = nullptr;
	}
public:
	void increment()
	{
		++use_count;
	}
	void decrement()
	{
		if (--use_count == 0)
		{
			delete this; 
		}
	}
private:
	char *m_data;
	size_t use_count;
};

现在的字符串类私有成员只包含一个字符串计数引用类:

在这里插入图片描述
字符串内部行为发生了变化,由原先的字符指针变成了一个类的指针类型
m_rep是指针调动构造方法,调动了String_rep的构造方法,有两个成员m_data和use_count 共同组成一个字符串引用计数器的对象,但是没名字,它会根据字符串内容开辟空间。最后返回的地址是m_rep。最终手动申请的动态对象最终由m_rep指向。
在这里插入图片描述
把对象初始化好,m_rep要加计数,因此专门给它设计了两个方法。
在这里插入图片描述
实例化完这个对象后,就要调动加计数器更新。
对于新的s1对象,这两个对象没有关联的地方,都有各自的引用计数器指针,互不干扰。
在这里插入图片描述
对于s2对象,进行浅拷贝赋值的时候,同时指向s的引用计数器空间,引用计数器会更新。所以引用计数器是一个对象,不仅负责对空间的计数,还有一个指针指向这个空间。
在这里插入图片描述
这样不同对象就拥有了不同的计数器。

但是析构的时候怎么析构?检查的时候会发现内存泄漏
因为new申请的空间都没释放。

对象要析构,就是减少引用计数。
在这里插入图片描述
但是减少引用计数之后并没有做任何操作
在这里插入图片描述
因此当计数器减为0的时候,给其设计一个自杀规则
在这里插入图片描述
自杀,防止了内存泄漏,其他对象析构的时候就会调用析构函数减少计数器数量,当最后一个对象析构的时候先调用自身析构方法,然后也紧接着要减少计数器,当等于0的时候,就delete this。

问题:这个this是谁?

这个this就是当前对象s内部的指针m_rep,m_rep是String_rep类型,先调自己的析构方法,而它的析构方法是先释放空间:
在这里插入图片描述
释放的是String_rep所指向的空间
在这里插入图片描述
但是先不能释放String_rep对象释放掉,那么留下的那个空间就会造成内存泄漏。所以先要把这个对象指针所管理的资源给释放掉。不然就没机会释放了。

**在此这里先释放m_data的空间(指针所指的资源),否则造成内存泄漏,也就是通过析构函数先回收资源,搞成空壳子之后再释放。**因此这里delete this先调用了String_rep自身的析构函数。

类里的东西属于静态的,它可以自己释放,new申请的叫动态的,需要手动释放(所谓动态内存管理)。

赋值操作怎么写?

就是说s2给s1赋值,呢就是说s1不在指向s,而是指向s2的空间,那么s的空间引用计数器就要减1,s2的空间引用计数器就要加1。
在这里插入图片描述

写时拷贝

对于这种情况,拷贝构造的时候,因为是浅拷贝,指向相同的空间,就是将s1的内容修改后,那么势必会影响s对象的修改。
在这里插入图片描述
多个对象指向了一个空间,若一个对象要修改内容,那么就不能共同使用一个空间了,称之为”写时拷贝
让这个要修改的对象单独去完成这个操作,把它深拷贝出来。就是新指向的空间引用计数器得加1,原先的空间引用计数器减1,让最终对象的引用计数指针指向新的引用计数空间
在这里插入图片描述
整体的代码结构:

// 深拷贝与浅拷贝相关代码
// An highlighted block
#include<iostream>
#include<string.h>

using namespace std;


//引用计数器类
class String_rep
{
	friend class String;
	friend ostream& operator<<(ostream &out, const String &s);
public:
	String_rep(const char *str = "") :use_count(0)
	{
		m_data = new char[strlen(str) + 1];
		strcpy(m_data, str);
	}
	String_rep(const String_rep &rep) :use_count(0)
	{
		m_data = new char[strlen(rep.m_data) + 1];
		strcpy(m_data, rep.m_data);
	}
	String_rep& operator=(const String_rep &rep);
	~String_rep()
	{
		delete[]m_data;
		m_data = nullptr;
	}
public:
	void increment()
	{
		++use_count;
	}
	void decrement()
	{
		if (--use_count == 0)
		{
			delete this; //自杀
		}
	}
private:
	char *m_data;
	size_t use_count;
};


/
class String
{
	friend ostream& operator<<(ostream &out, const String &s);
public:
	String(const char *str = "") :m_rep(new String_rep(str))
	{
		m_rep->increment();
	}
	String(const String &s) : m_rep(s.m_rep)
	{
		m_rep->increment();//增加引用计数
	}
	String& operator=(const String &s)
	{
		if (this != &s)
		{
			m_rep->decrement();
			m_rep = s.m_rep;
			m_rep->increment();
		}
		return *this;
	}
	~String()
	{
		m_rep->decrement();//减少引用计数
	}
public:
	//写时拷贝
	void to_upper()
	{
		String_rep *new_rep = new String_rep(*m_rep);
		m_rep->decrement();
		m_rep = new_rep;

		char *pch = m_rep->m_data;
		while (*pch != '\0')
		{
			if (*pch >= 'a' && *pch <= 'z')
				*pch -= 32;
			pch++;
		}
		m_rep->increment();
	}
private:
	String_rep *m_rep;
};

ostream& operator<<(ostream &out, const String &s)
{
	out << s.m_rep->m_data;
	return out;
}

void main()
{
	String s1("abc");
	String s2 = s1;

	cout << "s1 = " << s1 << endl;
	cout << "s2 = " << s2 << endl;

	s1.to_upper();

	cout << "s1 = " << s1 << endl;
	cout << "s2 = " << s2 << endl;
}
/*
void main()
{
//String t("xyz");
String s("abc");
String s1 = s;

String s2("xyz");
s1 = s2;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++——浅拷贝、深拷贝、写时拷贝详解 的相关文章

  • 访问私人成员[关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 通过将类的私有成员转换为 void 指针 然后转换为结构来访问类的私有成员是否合适 我认为我无权修改包含我需要访问的数据成员的类 如果不道德 我
  • 如何避免情绪低落?

    我有一个实现状态模式每个状态处理从事件队列获取的事件 根据State因此类有一个纯虚方法void handleEvent const Event 事件继承基础Event类 但每个事件都包含其可以是不同类型的数据 例如 int string
  • 当 contains() 工作正常时,xpath 函数ends-with() 工作时出现问题

    我正在尝试获取具有以特定 id 结尾的属性的标签 like span 我想获取 id 以 国家 地区 结尾的跨度我尝试以下xpath span ends with id Country 但我得到以下异常 需要命名空间管理器或 XsltCon
  • WPF 中的调度程序和异步等待

    我正在尝试学习 WPF C 中的异步编程 但我陷入了异步编程和使用调度程序的困境 它们是不同的还是在相同的场景中使用 我愿意简短地回答这个问题 以免含糊不清 因为我知道我混淆了 WPF 中的概念和函数 但还不足以在功能上正确使用它 我在这里
  • 为什么#pragma optimize("", off)

    我正在审查一个 C MFC 项目 在某些文件的开头有这样一行 pragma optimize off 我知道这会关闭所有以下功能的优化 但这样做的动机通常是什么 我专门使用它来在一组特定代码中获得更好的调试信息 并在优化的情况下编译应用程序
  • C - 找到极限之间的所有友好数字

    首先是定义 一对友好的数字由两个不同的整数组成 其中 第一个整数的除数之和等于第二个整数 并且 第二个整数的除数之和等于第一个整数 完美数是等于其自身约数之和的数 我想做的是制作一个程序 询问用户一个下限和一个上限 然后向他 她提供这两个限
  • 如何返回 json 结果并将 unicode 字符转义为 \u1234

    我正在实现一个返回 json 结果的方法 例如 public JsonResult MethodName Guid key var result ApiHelper GetData key Data is stored in db as v
  • C# 中的递归自定义配置

    我正在尝试创建一个遵循以下递归结构的自定义配置部分
  • 在数据库中搜索时忽略空文本框

    此代码能够搜索数据并将其加载到DataGridView基于搜索表单文本框中提供的值 如果我将任何文本框留空 则不会有搜索结果 因为 SQL 查询是用 AND 组合的 如何在搜索 从 SQL 查询或 C 代码 时忽略空文本框 private
  • Github Action 在运行可执行文件时卡住

    我正在尝试设置运行google tests on a C repository using Github Actions正在运行的Windows Latest 构建过程完成 但是当运行测试时 它被卡住并且不执行从生成的可执行文件Visual
  • 从库中捕获主线程 SynchronizationContext 或 Dispatcher

    我有一个 C 库 希望能够将工作发送 发布到 主 ui 线程 如果存在 该库可供以下人员使用 一个winforms应用程序 本机应用程序 带 UI 控制台应用程序 没有 UI 在库中 我想在初始化期间捕获一些东西 Synchronizati
  • Discord.net 无法在 Linux 上运行

    我正在尝试让在 Linux VPS 上运行的 Discord net 中编码的不和谐机器人 我通过单声道运行 但我不断收到此错误 Unhandled Exception System Exception Connection lost at
  • 将 xml 反序列化为类,list<> 出现问题

    我有以下 XML
  • C++ 复制初始化和直接初始化,奇怪的情况

    在继续阅读本文之前 请阅读在 C 中 复制初始化和直接初始化之间有区别吗 https stackoverflow com questions 1051379 is there a difference in c between copy i
  • C++ fmt 库,仅使用格式说明符格式化单个参数

    使用 C fmt 库 并给定一个裸格式说明符 有没有办法使用它来格式化单个参数 example std string str magic format 2f 1 23 current method template
  • 控制到达非 void 函数末尾 -wreturn-type

    这是查找四个数字中的最大值的代码 include
  • C - 直接从键盘缓冲区读取

    这是C语言中的一个问题 如何直接读取键盘缓冲区中的数据 我想直接访问数据并将其存储在变量中 变量应该是什么数据类型 我需要它用于我们研究所目前正在开发的操作系统 它被称为 ICS OS 我不太清楚具体细节 它在 x86 32 位机器上运行
  • 如何在 C++ BOOST 中像图形一样加载 TIFF 图像

    我想要加载一个 tiff 图像 带有带有浮点值的像素的 GEOTIFF 例如 boost C 中的图形 我是 C 的新手 我的目标是使用从源 A 到目标 B 的双向 Dijkstra 来获得更高的性能 Boost GIL load tiif
  • 防止索引超出范围错误

    我想编写对某些条件的检查 而不必使用 try catch 并且我想避免出现 Index Out of Range 错误的可能性 if array Element 0 Object Length gt 0 array Element 1 Ob
  • 使用按位运算符相乘

    我想知道如何使用按位运算符将一系列二进制位相乘 但是 我有兴趣这样做来查找二进制值的十进制小数值 这是我正在尝试做的一个例子 假设 1010010 我想使用每个单独的位 以便将其计算为 1 2 1 0 2 2 1 2 3 0 2 4 虽然我

随机推荐

  • 生活之你为什么不学习

    最近在别人的空间 我看到了八句话 你有什么理由不学习 感觉说得挺有鸡血的 1 你不能把这个世界让给你所鄙视的人 2 成功的速度一定要超过父母老去的速度 3 可怕的不是别人比你优秀 而是比你优秀的人比你还努力 4 我努力的目的是让我的妈妈买东
  • 【华为OD机试python】按身高和体重排队【2023 B卷

    华为OD机试 真题 点这里 华为OD机试 真题考点分类 点这里 题目描述 某学校举行运动会 学生们按编号 1 2 3 n 进行标识 现需要按照身高由低到高排列 对身高相同的人 按体重由轻到重排列 对于身高体重都相同的人 维持原有的编号顺序关
  • 最全的交叉编译Makefile讲解

    最近正在搞交叉编译 参考很多博客 学习了一下Makefile的编写 记录一下Makefile内代码是什么意思 代码如下 简单的hello ko的makefile ifneq KERNELRELEASE obj m hello o else
  • [CUDA] 快速入门CUDA(1)-基本了解和HelloWorld

    CUDA基础 文章目录 CUDA基础 1 CUDA简介 2 GPU和CPU架构的不同之处 3 查看GPU硬件信息 4 需要建立的基本概念 5 总结 1 CUDA简介 CUDA的全程是Computer Unified Device Archi
  • 树莓派4B(buster)的源更换为北外(清华)国内源

    树莓派4B buster 的源更换为北外 清华 国内源 1 登陆到树莓派 ssh pi your raspi IP 2 备份源文件 sudo cp etc apt sources list etc apt sources list bak
  • GoogLeNet论文详解

    GoogLeNet 1 Introduction 得益于深度学习的优势和更强大的卷积神经网络的出现 图像分类和目标检测的准确率发生了令人意想不到的进步 在2014年的ILSVRC比赛中 GoogLeNet取得了第一名的成绩 所用模型参数不足
  • 详解如何修改Linux文件权限

    参考 详解如何修改Linux文件权限 Linux文件权限详解 在Linux系统中 可以使用chmod命令来修改文件的权限 该命令用于更改文件或目录的读取 r 写入 w 和执行 x 权限 以下是一些详细的说明和示例 使用数字表示权限 r 读取
  • Golang教程:(十六)结构体

    原文 https golangbot com structs 欢迎来到Golang系列教程的第十六篇 什么是结构体 结构体 struct 是用户自定义的类型 它代表若干字段的集合 有些时候将多个数据看做一个整体要比单独使用这些数据更有意义
  • element el-table render-header自定义复选框

    项目中需要对列表数据进行批量处理 表头增加复选框 并关联列表数据 el table提供解决方法 实现多选非常简单 手动添加一个el table column 设type属性为selection即可 尝试后在我的项目中不适用 于是找到另一种r
  • Redis缓存与数据库双写一致性解决方案

    目录 1 冤孽的诞生 1 1 需求起因 1 2 策略之争 2 标准解决方案 2 1 延时双删策略 2 2 异步更新缓存 基于订阅binlog的同步机制 3 基于binlog订阅实现步骤 3 1 准备材料 3 2 代码实现 1 冤孽的诞生 1
  • python两个二维数组加法_python中利用numpy.array()实现俩个数值列表的对应相加方法...

    python中利用numpy array 实现俩个数值列表的对应相加方法 小编想把用python将列表 1 1 1 1 1 1 1 1 1 1 和 列表 2 2 2 2 2 2 2 2 2 2 对应相加成 3 3 3 3 3 3 3 3 3
  • [嵌入式linux]PCIe 热拔插(rescan)

    linux下可通过 sys bus pci devices 0000 bus number device number function number 目录下的节点进行热拔插操作 板子上电前PCIe插槽有一块NVME的固态硬盘 0 1985
  • SDIO接口协议(MMC SD WIFI GPS 以太网)

    一文搞懂SDIO 曼巴精神传承人的博客 CSDN博客 sdio SDIO协议 觅食小鱼的博客 CSDN博客 sdio协议 传统的SD存储卡只有一排引脚 包括一个3 3V电源VDD 用于默认速度
  • 使用ReactiveCocoa实现iOS平台响应式编程

    使用ReactiveCocoa实现iOS平台响应式编程 TIGER IOS 10 使用ReactiveCocoa实现iOS平台响应式编程 ReactiveCocoa和响应式编程 在说ReactiveCocoa之前 先要介绍一下FRP Fun
  • linux arm mmu基础

    ARM MMU页表框架 先上一张arm mmu的页表结构的通用框图 以下的论述都由该图来逐渐展开 以上是arm的页表框图的典型结构 即是二级页表结构 其中第一级页表 L1 是由虚拟地址的高12bit bits 31 20 组成 所以第一级页
  • 常用的IDEA插件

    IDEA是程序员用的最多的开发工具 很多程序员想把它打造成一站式开发工具 于是安装了各种各样的插件 通过插件在IDEA中完成各种操作 无需安装其他软件 确实很方便 今天给大家分享下我平时常用的IDEA插件 个个是精品 Key Promote
  • 今天我花一个通宵的时间安装Windows11系统居然失败,忍不住哭了!

    个人主页 极客小俊 作者简介 web开发者 设计师 技术分享博主 希望大家多多支持一下 我们一起进步 如果文章对你有帮助的话 欢迎评论 点赞 收藏 加关注 我们就不要废话了 直接上主题吧 Windows11 系统安装 提示此电脑无法运行完美
  • ospfv2详解

    文章目录 OSPFV2 技术背景 OSPF基本信息 OSPF 如何完成收敛 SPF计算 DR与BDR DR与BDR的选举 泛洪机制 影响OSPF邻接关系连理原因 1 状态 2 报文 参数 3 物理故障 lsa优先cost及防止环路 ospf
  • 通过doi可以检索到文献_经验丨我如何获得英文文献

    前阵子有师妹问我如何找英文文献 那这周就来分享分享我的经验 希望能有所帮助 那我们开始吧 本期推送目标 查找并下载目标英文文献 其实这里包含两个部分 第一个是找到想要的英文文献 第二个才是下载 如果已知目标文献信息 仅仅想要下载可以直接在往
  • C++——浅拷贝、深拷贝、写时拷贝详解

    C 浅拷贝 深拷贝 写时拷贝详解 浅拷贝与深拷贝 解决浅拷贝的问题 引用计数 写时拷贝 浅拷贝与深拷贝 用String类模拟用 将 0 拷贝进去 调用系统默认的拷贝构造函数 结果就是内容相同 地址相同 说明这个方法是浅拷贝方法 浅拷贝方法带