C++ primer智能指针(HasPtr)实现

2023-11-19

智能指针显然是C++吸引人的地方之一,必须掌握。看了《C++primer》,里面着重讲了智能指针的实现方式。

书中说到:

    “HasPtr(注:就是自定义的智能指针)在其它方面的行为与普通指针一致。具体而言,复制对象时,副本和原对象将指向同一基础对象。如果通过一个副本改变基础对象,则通过另一个对象访问的值也会改变。

      新的HasPtr类需要一个析构函数来删除指针。但是,析构函数不能无条件的删除指针。”

     条件就是引用计数。如果该对象被两个指针所指,那么删除其中一个指针,并不会调用该指针的析构函数,因为此时还有另外一个指针指向该对象。看来,智能指针主要是预防不当的析构行为,防止出现悬垂指针。


如上图所示,HasPtr就是智能指针,U_Ptr为计数器,定义如下:

class U_Ptr {
	friend class HasPtr;
	int *ip;
	size_t use;
	U_Ptr(int *p) :
		ip(p), use(1) {
		cout << "U_ptr constructor called !" << endl;
	}
	~U_Ptr() {
		delete ip;
		cout << "U_ptr distructor called !" << endl;
	}
};
里面有个变量use和指针ip: use记录了*ip对象被多少个HasPtr对象所指。假设现在又两个HasPtr对象p1,p2指向了U_Ptr,那么现在我delete  p1,use变量将自减1,   U_Ptr 不会析构,那么U_Ptr指向的对象也不会析构,那么p2仍然指向了原来的对象,而不会变成一个悬空指针。当delete p2的时候,use变量将自减1,为0。此时,U_Ptr对象进行析构,那么U_Ptr指向的对象也进行析构,保证不会内存泄露。  

包含指针的类需要特别注意复制控制,原因是复制指针时只复制指针中的地址,而不会复制指针指向的对象。

大多数C++类用三种方法之一管理指针成员

    (1)不管指针成员。复制时只复制指针,不复制指针指向的对象。当其中一个指针把其指向的对象的空间释放后,其它指针都成了悬浮指针。这是一种极端

   (2)当复制的时候,即复制指针,也复制指针指向的对象。这样可能造成空间的浪费。因为指针指向的对象的复制不一定是必要的。

   (3)第三种就是一种折中的方式。利用一个辅助类来管理指针的复制。原来的类中有一个指针指向辅助类,辅助类的数据成员是一个计数器和一个指针(指向原来的)(此为本次智能指针实现方式)。

     其实,智能指针的引用计数类似于java的垃圾回收机制:java的垃圾的判定很简答,如果一个对象没有引用所指,那么该对象为垃圾。系统就可以回收了

# include <iostream>
using namespace std;
class U_Ptr {
	friend class HasPtr;
	int *ip;
	size_t use;
	U_Ptr(int *p) :
		ip(p), use(1) {
		cout << "U_ptr constructor called !" << endl;
	}
	~U_Ptr() {
		delete ip;
		cout << "U_ptr distructor called !" << endl;
	}
};
class HasPtr {
public:
	HasPtr(int *p, int i) :
		ptr(new U_Ptr(p)), val(i) {
		cout << "HasPtr constructor called ! " << "use = " << ptr->use << endl;
	}
	HasPtr(const HasPtr& orig) :
		ptr(orig.ptr), val(orig.val) {
		++ptr->use;
		cout << "HasPtr copy constructor called ! " << "use = " << ptr->use
				<< endl;
	}
	HasPtr& operator=(const HasPtr&);
	~HasPtr() {
		cout << "HasPtr distructor called ! " << "use = " << ptr->use << endl;
		if (--ptr->use == 0)
			delete ptr;
	}
	int *get_ptr() const {
		return ptr->ip;
	}
	int get_int() const {
		return val;
	}
	void set_ptr(int *p) const {
		ptr->ip = p;
	}
	void set_int(int i) {
		val = i;
	}
	int get_ptr_val() const {
		return *ptr->ip;
	}
	void set_ptr_val(int i) {
		*ptr->ip = i;
	}
private:
	U_Ptr *ptr;
	int val;
};
HasPtr& HasPtr::operator =(const HasPtr &rhs) {     //注意,这里赋值操作符在减少做操作数的使用计数之前使rhs的使用技术加1,从而防止自我赋值
	++rhs.ptr->use;
	if (--ptr->use == 0)
		delete ptr;
	ptr = rhs.ptr;
	val = rhs.val;
	return *this;
}
int main() {
	int *pi = new int(0);
	HasPtr *hpa = new HasPtr(pi, 100);
	HasPtr *hpb = new HasPtr(*hpa);
	HasPtr *hpc = new HasPtr(*hpb);
	HasPtr hpd = *hpa;

	cout << hpa->get_ptr_val() << " " << hpb->get_ptr_val() << endl;
	hpc->set_ptr_val(10000);
	cout << hpa->get_ptr_val() << " " << hpb->get_ptr_val() << endl;
	hpd.set_ptr_val(10);
	cout << hpa->get_ptr_val() << " " << hpb->get_ptr_val() << endl;
	delete hpa;
	delete hpb;
	delete hpc;
	cout << hpd.get_ptr_val() << endl;
	return 0;
}

这里的赋值操作符比较麻烦,且让我用图表分析一番:

假设现在又两个智能指针p1, p2,一个指向内容为42的内存,一个指向内容为100的内存,如下图:


现在,我要做赋值操作,p2 = p1。对比着上面的

HasPtr& HasPtr::operator =(const HasPtr &rhs)
此时,rhs就是p1,首先将p1指向的ptr的use加1,
++rhs.ptr->use;
然后,做:

if (--ptr->use == 0)
		delete ptr;
因为,原先p2指向的对象现在p2不在指向,那么该对象就少了一个指针去指,所以,use做自减1;

此时,条件成立。因为u2的use为1。那么,运行U_Ptr的析构函数,而在U_Ptr的析构函数中,做了delete ip操作,所以释放了内存,不会有内存泄露的问题。

接下来的操作很自然,无需多言:

ptr = rhs.ptr;
val = rhs.val;
return *this;
做完赋值操作后,那么就成为如下图所示了。红色标注的就是变化的部分:


而还要注意的是,重载赋值操作符的时候,一定要注意的是,检查自我赋值的情况。

如图所示:


此时,做p1 = p1的操作。那么,首先u1.use自增1,为2;然后,u1.use自减1,为1。那么就不会执行delete操作,剩下的操作都可以顺利进行。按《C++ primer》说法,“这个赋值操作符在减少左操作数的使用计数之前使rhs的使用计数加1,从而防止自身赋值”。哎,反正我是那样理解的。当然,一来就可以按常规那样:

if(this == &rhs)
    return *this;

运行结果:

U_ptr constructor called !
HasPtr constructor called ! use = 1
HasPtr copy constructor called ! use = 2
HasPtr copy constructor called ! use = 3
HasPtr copy constructor called ! use = 4
0 0
10000 10000
10 10
HasPtr distructor called ! use = 4
HasPtr distructor called ! use = 3
HasPtr distructor called ! use = 2
10
HasPtr distructor called ! use = 1
U_ptr distructor called !



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

C++ primer智能指针(HasPtr)实现 的相关文章

  • 什么定义了类型的大小?

    ISO C 标准规定 sizeof char lt sizeof short lt sizeof int lt sizeof long 我在 BIT Linux mint 19 1 上使用 GCC 8 大小为long int is 8 我正
  • 如何使用 Entity Framework 和 Identity 解决对象处置异常 ASP.NET Core

    我正在尝试编写一个控制器 该控制器接收来自 AJAX 调用的请求并通过 DBContext 对数据库执行一些调用 但是 当我发出命令时var user await GetCurrentUserAsynch 在对 DBContext 的任何调
  • 如何通过覆盖 MSBuild 目标来防止外语资源生成?

    我正在致力于减少大型 C ASP NET 解决方案的编译时间 我们的解决方案使用通常的 resx 文件方法翻译成大约十几种外语 这些资源文件的解析和编译极大地减慢了我们的编译时间 并且是日常的挫败感 我知道可以创建自定义资源提供程序并摆脱
  • Qt/c++ 随机字符串生成[重复]

    这个问题在这里已经有答案了 我正在创建一个应用程序 需要生成多个随机字符串 几乎就像一个由一定长度的 ASCII 字符组成的唯一 ID 这些字符混合有大写 小写 数字字符 有没有 Qt 库可以实现这一点 如果没有 在纯 C 中生成多个随机字
  • 如何使用c#从数据桶中获取所有文档?

    如何获取数据桶中的所有文档 我尝试过一个示例 但我只能获得一个特定的文档 这是我的代码 CouchbaseClient oclient oclient new CouchbaseClient vwspace data bucket name
  • 预编译头和 Visual Studio

    有没有办法设置 Visual Studio 解决方案参数 以便它只创建预编译头而不构建整个解决方案 具体来说 它是一个巨大的 C 解决方案 本身有许多项目 谢谢 仅选择 pch 创建者源文件 通常是 stdafx cpp 然后编译该文件 C
  • 无法将参数从 `const char *` 转换为 `char *`

    鉴于此代码 void group build int size std string ips Build the LL after receiving the member list from bootstrap head new memb
  • 在 .NET Core 中从 HttpResponseMessage 转换为 IActionResult

    我正在将之前在 NET Framework 中编写的一些代码移植到 NET Core 我有这样的事情 HttpResponseMessage result await client SendAync request if result St
  • QSpinBox 输入 NaN 作为有效值

    我正在尝试扩展 QSpinBox 以能够输入 NaN 或 nan 作为有效值 根据文档 我应该使用 textFromValue valueFromText 和 validate 函数来完成此操作 但我无法让它工作 因为它仍然不允许我输入除数
  • 如何调试.NET Windows Service OnStart方法?

    我用 NET 编写的代码仅在作为 Windows 服务安装时才会失败 该故障甚至不允许服务启动 我不知道如何进入 OnStart 方法 如何 调试 Windows 服务应用程序 http msdn microsoft com en us l
  • 原子存储抛出错误

    我最近升级到了 C 11 兼容编译器 并且尝试将一些代码从 boost 更新到 c 11 标准 我在使用atomic store转换一些代码时遇到了问题 这是一些简单的测试代码 似乎会引发编译器错误 int main std shared
  • 检测反射 DLL 注入

    在过去的几年中 恶意软件 以及一些渗透测试工具 如 Metasploit 的 meterpreter 负载 已经开始使用反射 DLL 注入 PDF http www harmonysecurity com files HS P005 Ref
  • ASP.net WebForms - 在标记中使用 GetRouteUrl

    我一直在尝试弄清楚如何将路由功能与 ASP net 4 0 WebForms 一起使用 我将一条路线添加到我的路线集合中 void Application Start RegisterRoutes RouteTable Routes voi
  • 节点*链表中的下一个

    我是数据结构和算法的新手 我遇到了以下代码 typedef struct node int data node next 谁能告诉我为什么我们要声明节点 next next 不能声明为 int next 吗 因为你希望能够做到n gt ne
  • 本地时间的内存需要释放吗?

    void log time t current time 0 tm ptm localtime current stuf 只是想确定 我是否需要在方法结束时释放 tm 指针分配的内存 不 你不应该释放它 该结构是静态分配的 检查文档 htt
  • 应在堆栈上分配的最大数量

    我一直在寻找堆栈溢出有关应在堆栈上分配的最大内存量的指南 我看到了堆栈与堆分配的最佳实践 但没有关于应该在堆栈上分配多少以及应该在堆上分配多少的指南 有什么想法 数字可以作为指导吗 什么时候应该在堆栈上分配 什么时候应该在堆上分配 多少才算
  • 从具有相同属性的另一个对象创建对象

    我有一个 C 对象 可以说有 20 个属性 它是数据契约的一部分 我还有另一个具有类似属性的业务实体 我想从响应对象中填充该实体 除了将一个对象的每个属性分配给另一个对象的相应属性之外 还有其他方法可以做到这一点吗 是的 看看自动映射器 h
  • 如何在控制台程序中获取鼠标位置?

    如何在 Windows 控制台程序中用 C 获取鼠标单击位置 点击时返回鼠标位置的变量 我想用简单的文本命令绘制一个菜单 这样当有人点击时 游戏就会注册它并知道位置 我知道如何做我需要做的一切 除了单击时获取鼠标位置 您需要使用 Conso
  • 使用任务的经典永无止境的线程循环?

    给出了一个非常常见的线程场景 宣言 private Thread thread private bool isRunning false Start thread new Thread gt NeverEndingProc thread S
  • 使用C标准数学库精确计算标准正态分布的CDF

    标准 C 数学库不提供计算标准正态分布 CDF 的函数 normcdf 然而 它确实提供了密切相关的函数 误差函数 erf 和互补误差函数 erfc 计算 CDF 的最快方法通常是通过误差函数 使用预定义常量 M SQRT1 2 来表示 d

随机推荐

  • 【批处理DOS-CMD命令-汇总和小结】-CMD窗口的设置与操作命令-关闭cmd窗口、退出cmd环境(exit、exit /b、goto :eof)

    一 对exit命令和goto命令的基本认知 打印exit命令的帮助信息 执行命令 exit C Users Administrator gt exit 退出 CMD EXE 程序 命令解释器 或当前批处理脚本 EXIT B exitCode
  • php文件打印服务器,PHP打印到服务器端打印机

    我想用PHP打印到服务器端打印机 我发现了类似的示例代码 它们大多都使用相同的API函数来执行此任务 当我在我的服务器上运行它来测试它所说的代码时 PHP致命错误 调用未定义的函数printer open 所以我发现至少有三种不同版本的ph
  • keras.layers.Conv2D 与tf.layers.Conv2D 的兼容性: AttributeError: ‘tuple‘ object has no attribute ‘layer‘

    结论 keras layers Conv2D 与 tf layers Conv2D有相同的参数设置模式 keras layers Conv2D 可以兼容处理 tf layers Conv2D得到的tensor tf layers Conv2
  • 表示数值的字符串(含思路解答示意图)【剑指offer——JAVA实现】

    题目描述 请实现一个函数用来判断字符串是否表示数值 包括整数和小数 例如 字符串 100 5e2 123 3 1416 和 1E 16 都表示数值 但是 12e 1a3 14 1 2 3 5 和 12e 4 3 都不是 解法一 思路 状态机
  • 三、react中类组件和函数组件

    简介 本篇我们只要介绍react中类组件与函数组件两种组件的写法 两者的优缺点 同时对在我们的项目开发中该使用类组件还是函数组件进行思考分析 废话不多说进入正题 类组件 设计思路 类组件时面向对象编程的思想 在其中我们去设计类组件时使用st
  • 【每日一题】leetcode 二叉树层序遍历 - 介绍

    层序遍历 遍历顺序为 F B G A D I C E H 一层一层遍历 代码 import java util ArrayList import java util LinkedList import java util List impo
  • 《软件调试的艺术》学习笔记——GDB使用技巧摘要(1)

    软件调试的艺术 因为名是The Art of Debugging with GDB DDD and Eclipse 作者是美国的Norman Matloff和Peter Jay Salzman 中文版由张云翻译 是人邮出版社图灵程序设计丛书
  • 机器学习2-线性回归

    一 矩阵求导公式 1 总体情况 2 分子布局 Numerator layout 和分母布局 Denominator layout 首先我们常说 y 对 x 求导 这里的 y 和 x 均默认为列向量 y为 mx1 x为 nx1 1 分子布局
  • stm32—外部中断、中断和事件的区别

    目录 EXTI 简介 EXTI 框图 1 中断的线路 1 2 3 4 5 2 产生事件线路 1 2 3 6 7 8 疑惑 中断和事件的区别 1 硬件级与软件级 2 事件不一定产生中断 外部中断 事件线映射 EXTI 配置步骤 初始化函数 E
  • sklearn:卡方分布输入不能是负数

    今天使用sklearn进行特征选择的时候出现一个错误 ValueError Input X must be non negative 找了stackoverflow 原来是卡方验证不能用于负值 卡方分布是通过统计当前变量的频次 和目标变量的
  • 【程序人生】底层程序员,出局

    底层程序员 出局 不如去送外卖 这是徐亮和同事们常开的一个玩笑 入职两三个月 最初的激情退去 在加完班的夜晚 他疲惫地躺在床上 经常自嘲式地想起这个玩笑 送外卖是搬运食物 自己是搬运代码 都不产出新的东西 在深圳 每个人都走得很快 这是徐亮
  • kafka的安装和使用

    ZooKeeper简介 ZooKeeper 是一个为分布式应用所设计的分布的 开源的 java 协调服务 分布式的应用可以建立在同步配置管理 选举 分布式锁 分组和命名等服务的更高级别的实现的基础之上 ZooKeeper 意欲设计一个易于编
  • C语言(二十一)

    1 查找指定字符 本题要求编写程序 从给定字符串中查找某指定的字符 输入 输入待查找的字符c以及字符串s 输出 找到则输出字符c在字符串s中所对应的最大下标index 否则输出 Not Found 优化目标 无 include
  • TCP/IP详解 卷1:协议 学习笔记 第二十九章 网络文件系统

    NFS 网络文件系统 使客户可以透明地访问服务器上的文件和文件系统 NFS的基础是RPC 两个常用的网络编程API socket和TLI 运输层接口 Transport Layer Interface 通信的双方可使用不同的API RPC可
  • 蚁剑的使用以及用蚁剑做一道ctf题

    一 蚁剑的介绍及下载 1 蚁剑是一款和菜刀相像的shell控制端软件 主要面向于合法授权的渗透测试安全人员以及进行常规操作的网站管理员 2 蚁剑的下载 这是gethub的官方下载地址 供大家下载 3 蚁剑的安装 点击初始化就完成安装 再次点
  • Linux进程管理:deadline调度器

    一 概述 实时系统是这样的一种计算系统 当事件发生后 它必须在确定的时间范围内做出响应 在实时系统中 产生正确的结果不仅依赖于系统正确的逻辑动作 而且依赖于逻辑动作的时序 换句话说 当系统收到某个请求 会做出相应的动作以响应该请求 想要保证
  • Jib使用小结(Maven插件版)

    小结三 多次构建后 积累的无用镜像 如下所示 构建多次后 本地会遗留多个名为 tag也是的镜像 root maven hellojib docker images REPOSITORY TAG IMAGE ID CREATED SIZE b
  • 懒人式迁移服务器深度学习环境(完全不需要重新下载)

    换服务器了 想迁移原来服务器上的深度学习环境 但又觉得麻烦懒得重新安装一遍anaconda pytorch 有没有办法能不费吹灰之力直接迁移 接下来跟着我一起 懒汉式迁移 本方法适用于在同一内网下的两台服务器之间互相迁移 不在同一局域网下的
  • 【华为OD统一考试B卷

    在线OJ 已购买本专栏用户 请私信博主开通账号 在线刷题 运行出现 Runtime Error 0Aborted 请忽略 华为OD统一考试A卷 B卷 新题库说明 2023年5月份 华为官方已经将的 2022 0223Q 1 2 3 4 统一
  • C++ primer智能指针(HasPtr)实现

    智能指针显然是C 吸引人的地方之一 必须掌握 看了 C primer 里面着重讲了智能指针的实现方式 书中说到 HasPtr 注 就是自定义的智能指针 在其它方面的行为与普通指针一致 具体而言 复制对象时 副本和原对象将指向同一基础对象 如