【复读EffectiveC++21】条款21:必须返回对象时,别妄想返回其reference

条款21:必须返回对象时,别妄想返回其reference

此条款,也我刚刚工作时踩过的坑,一个功能总是莫名奇妙的数据丢失,调查的时候就是返回值指针总是在特定逻辑下返回NULL,就是因为我返回的是一个局部变量。
跟随着上一个条款的思路延申,看看引用还有那些使用注意。

一、三种错误方法

用原书例子,这里有一个无理数的类,它包含将两个有理数相乘的函数:

class Rational
{
public:Rational(int numerator = 0, int denominator = 1);
private:int n, d; //分子和分母friend const Rational operator*(const Rational& lhs, const Rational& rhs);	//划重点
};

在这里,重载乘号运算符(Operator*) 为值传递方式返回结果。
当看到值传递的时候,看过条款20的人都会开始考虑开销问题,然后就开始想方设法的开始使用引用传递来处理,比如以下实现以下操作:

Rational a(1, 2);
Rational b(3, 5);
Rational c = a * b;

不可避免的问题是,如果operator*即将返回一个有理数的引用,它必须自己创建出来这个有理数。

1、在栈上创建reference指向的对象

大聪明一号,选择通过定义一个本地变量来完成栈上的对象创建,实现方法如下:

const Rational& operator*(const Rational& lhs, const Rational& rhs)
{Rational result(lhs.n * rhs.n, lhs.d * rhs.d); // warning! 糟糕的代码return result;
}

这种方法对么,当然不对,其原因是:

  • 我们的目标是避免调用构造函数,但是这里的result必须被构造出来。
  • 而且,这个函数所返回指向result的引用,是一个局部对象,当函数退出的时候,这个对象就会随之销毁。

因此,不仅目的没有有效达到,同时,任何使用这个函数的返回值的调用者都将会马上进入未定义行为的范围,请排除这种方法!

2、在堆上创建reference指向的对象

比较聪明的大聪明二号,选择通过在堆内构造一个对象,并返回引用指向它,实现方法如下:

const Rational& operator*(const Rational& lhs, const Rational& rhs)
{Rational *result = new Rational(lhs.n*rhs.n, lhs.d*rhs.d); //更糟糕的写法return *result;//虽然编译器不报错,但是逻辑上是错误的
}

这种方法对么,当然也不对,要考虑以下内容:

  • 避免调用构造函数的目的,同样没有有效达成。
  • 而且,作为一个成熟的程序员,就要多考虑一步:谁该对着被你new出来的对象实施delete?

比如下面这种合理的使用场景,内存泄漏将不可避免:

Rational w, x, y, z;
w = x*y*z; //相当于operator*(operator*(x,y),z);

请问 operator*(x,y) 有指针接么?答案是 没有

3、为reference创建 static对象

更聪明的大聪明三号,选择了静态对象(static);
他的考虑是 静态对象具有 只需要调用一次构造函数,其余的构造函数将避免调用 的特点,利用此可以达成 避免调用构造 函数目标;
因此,他是这么做的:

const Rational& operator*(const Rational& lhs, const Rational& rhs)
{	// warning, 又一堆烂代码static Rational result; //静态局部变量result=...; //将lhs乘以rhs,然后将结果保存在result中return *result; //虽然编译器不报错,但是逻辑上是错误的
}

像所有使用静态对象的设计一样,这种方法增加了对于线程安全的梳理工作,但这个缺点是比较明显的。
为了看一下更深层次的缺陷,考虑下面这些完全合理的客户代码:

bool operator==(const Rational& lhs, const Rational& rhs); // for Rationals
Rational a, b, c, d;
...
if ((a * b) == (c * d)) {//乘积相等,做相应动作
} else {//不相等,做相应动作
}

当运行起来就会发现一个bug,即 表达式 ( (ab) == (cd) ) 的求值结果总为 true 。
这是为什么?
一句话:(a * b) 和 (c * d)都修改了同一个静态对象。

二、正确方法

一个“必须返回新对象”的函数的正确写法是:让函数返回新的对象。对Rational的opertaor*函数来说,其实现如下面的代码(或者与其等价的代码):

inline const Rational operator*(const Rational& lhs, const Rational& rhs)
{return Rational(lhs.n * rhs.n, lhs.d * rhs.d);
}

在返回一个引用还是返回一个对象之间做决定时,你的工作是选择 能够提供正确行为 的那个;
对于“如何使这个选择有尽可能小的开销”这个问题的解决,让编译器供应商去努力把。

三、总结

绝不要返回指针/引用指向一个局部stack对象,或返回一个引用指向heap-allocated对象,或者返回一个引用/指针指向一个静态局部变量。
条款4已经为“在单线程中合理返回引用指向一个静态局部变量”提供了一份设计实例。

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

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

相关文章

css属性Clip-path

clip-path 允许你定义一个元素的可视区域的剪切形状。换句话说,你可以使用这个属性来裁剪或隐藏元素的一部分,使其只显示特定形状的区域,比如圆形、椭圆形、多边形或更复杂的 SVG 路径。 基本用法 clip-path 可以接受多种值,包括…

非对称加密算法RSA的OpenSSL代码实现Demo

目录 1 RSA简介 1.1 RSA算法介绍 1.2 RSA算法的速度与安全性 1.3 RSA存储格式 1.3.1 PKCS#1 标准主要用于 RSA密钥,其RSA公钥和RSA私钥PEM格式 1.3.2 PKCS#8 标准定义了一个密钥格式的通用方案,其公钥和私钥PEM格式 2 OpenSSL代码实现 2.1 生…

WSL 2 Oracle Linux 9.1 安装配置

文章目录 环境使用体验安装 Oracle Linux 9.1修改默认存储路径默认 root 用户登录启用 systemd启用 SSH 连接WSL 无法 ping 通宿主机和域名WSL 使用主机代理(测试通过)WSL 常用命令 环境 OS:Win11 24H2 (OS 内部版本26120.1252) wsl --versio…

闭着眼就能学会的装饰器

目录 一,闭包 1,闭包函数含义以及三要素 2,定义一个简单的闭包 二,装饰器 1,装饰器的作用场景以及特点 2,定义一个简单的装饰器 1,需求1 2,需求2 三,装饰器的实…

初学51单片机之指针基础与串口通信应用

开始之前推荐一个电路学习软件,这个软件笔者也刚接触。名字是Circuit有在线版本和不在线版本,这是笔者在B站看视频翻到的。 Paul Falstadhttps://www.falstad.com/这是地址。 离线版本在网站内点这个进去 根据你的系统下载你需要的版本红线的是windows…

华盈生物-“表面等离子共振(SPR)技术如何进行靶点验证:揭秘靶点锁定的科学魔法”

在药物开发和生物研究中,靶点验证是一个至关重要的步骤。表面等离子共振(SPR)技术以其高灵敏度和实时监测能力,成为了靶点验证的理想工具。今天,我们将揭示SPR技术在靶点验证中的神奇应用,让我们一起看看它…

第九讲:POU与变量基础

POU(Program Organization Unit)的分类 一、定义及分类 POU即程序组成单元 二、三种POU的作用 1、功能/功能快:看作算法 功能块的POU是比较复杂的指令 三、功能块POU和功能POU的区别 1、理解功能POU(对比) 不添加实例名,就不需要去建立变量,所以就不会占到内存。 因…

算法题目整合4

文章目录 122. 大数减法123. 滑动窗口最大值117. 软件构建124. 小红的数组构造125. 精华帖子126. 连续子数组最大和 122. 大数减法 题目描述 以字符串的形式读入两个数字,编写一个函数计算它们的差,以字符串形式返回。输入描述 输入两个数字&#xff…

物联网专业创新人才培养体系的探索与实践

一、引言 随着物联网(IoT)技术的迅猛发展,物联网领域的人才需求日益增加。物联网技术作为新一轮信息技术革命的核心,已经渗透到社会生活的各个领域,对推动经济转型升级、提升国家竞争力具有重要意义。因此&#xff0c…

VUE之---slot插槽

什么是插槽 slot 【插槽】, 是 Vue 的内容分发机制, 组件内部的模板引擎使用slot 元素作为承载分发内容的出口。slot 是子组件的一个模板标签元素, 而这一个标签元素是否显示, 以及怎么显示是由父组件决定的。 VUE中slot【插槽】…

Postman的高级功能

Postman是一款功能强大的API测试工具,它提供了许多高级功能来帮助开发者和测试人员更高效地进行API测试和开发。以下是Postman在API测试中的一些高级功能: 1. 集合和文件夹 集合:用于组织相关的API请求。文件夹:在集合内部进一步…

Huffman编码和译码

Huffman编码(Huffman Coding),又称霍夫曼编码或赫夫曼编码,是一种用于无损数据压缩的熵编码(权编码)算法,由大卫霍夫曼(David A. Huffman)在1952年发明。Huffman编码属于可变字长编码(VLC)的一种,其基本思想是根据字符在数据中出现的频率来分配不同长度的编码,出现…

自己开发软件实现网站抓取m3u8链接

几天前一个同学说想下载一个网站的视频找不到连接,问我有没有什么办法,网站抓取m3u8链接 网页抓取m3u8链接。当时一听觉得应该简单,于是说我抽空看看。然后就分析目标网页,试图从网页源码里找出连接,有的源代码直接有,但是有的没有…

(二)C++之类与对象

一.类的申明 class 类名 { private: 私有的数据和成员函数; public: 公用的数据和成员函数; protected: 保护的数据和成员函数 };二.类的成员函数 构造函数(类的初始化,创建类时自动调用;初始化表,this指针) 默认构造函数 A();…

语法糖的setup和onMounted

遇到的问题:利用:style绑定响应式变量并结合css来动态更换颜色,绑定的响应式变量无法正常渲染 结论:本人将:style绑定响应式变量的值写在onMounted里面了,一个大失误,记录一下,利用setup语法糖默认初始化阶…

Redisson常用的数据结构及应用场景

Redisson 提供了一系列高级数据结构,这些数据结构封装了 Redis 的原生数据类型,提供了 Java API 的便利性和分布式特性。以下是 Redisson 中一些常用的数据结构,场景还在不断完善中: RBucket:这是一个简单的键值对存储…

Java二十三种设计模式-代理模式模式(8/23)

代理模式:为对象访问提供灵活的控制 引言 代理模式(Proxy Pattern)是一种结构型设计模式,它为其他对象提供一个代替或占位符,以控制对它的访问。 基础知识,java设计模式总体来说设计模式分为三大类&#…

Carefree为高性能设计仿真及人工智能提供一站式解决方案

在当今数字化转型的浪潮中,程易科技作为行业领先的科技创新企业,致力于为客户提供高效、安全、智能的研发资源服务。我们的研发资源服务平台集成了四大核心组件——研发资源统一门户、HPC高性能计算平台、远程可视化前后处理平台以及AI人工智能及算法平台…

Qt 实战(7)元对象系统 | 7.6、Q_DECLARE_METATYPE详解

文章目录 一、Q_DECLARE_METATYPE详解1、基本概念2、使用场景3、使用方法4、示例:QVariant使用自定义类型5、总结 前言: 在Qt框架的C开发中,Q_DECLARE_METATYPE是一个重要且常用的宏,它扮演着连接自定义类型与Qt元对象系统&#x…

Varjo XR-4系列现已获得达索3DEXPERIENCE平台官方支持

近日,全球领先的工业虚拟和混合现实解决方案提供商Varjo宣布,Varjo XR-4系列现已获得达索3DEXPERIENCE平台的本地支持。这种集成为工程师和设计师带来了先进的虚拟和混合现实功能,他们可以通过沉浸式技术创新并简化他们的3D工作流程。 在达索…