C++:EffectiveC++:Article21:必须返回对象时,别妄想返回其Reference

news2024/11/26 2:45:54

Article21:必须返回对象时,别妄想返回其Reference

  • 1. operator* 以by value 方式返回一个结果
  • 2. operator* 以 by Reference 方式返回一个结果
  • 3 定义static Rational 对象
  • 总结

本章主要介绍:函数返回值两种类型:值类型返回和引用返回通过实例来说明,这两种返回类型的差别。
在条款20中,已经领悟了 pass-by-value 的效率牵连层,很多人一心想要根除 传值的罪恶,坚定的追求 pass-by-reference,但是
这也许会犯下一个致命错误:开始传递一些 reference 指向其实并不存在的对象,这可不是件好事。

💚💚 例子: 考虑一个用以表示有理数(Rational numbers)的class ,内容包含一个函数计算两个有理数的乘积。

#include<iostream>
using namespace std;

//  g++ -std=c++11 main_3.18.cpp -fno-elide-constructors
class Rational {
public:

	Rational()
	{
		cout<< "default Rational construct......."<<this<< endl;
		
	}
	
	// 分子 numerator,分母:denominator
	Rational(int numerator =0,int denominator =1)
	{
		cout<< "Rational construct......."<<this<< endl;
		n = numerator;
		d= denominator;
	}
	
	~Rational() {
        std::cout << "destruct this object!: " << this<< std::endl;
    }
	
	Rational(const Rational& rat){
		std::cout << "Rational copy construct!: " << this << std::endl;
	}
	
	Rational(Rational&& rat){
		std::cout << "Rational rvalue construct!: " << this << std::endl;
	}

private:
	int n,d;
	friend const Rational operator* (const Rational& lhs,const Rational& rhs){
		std::cout << "Rational operator*: "<< std::endl;
	}
	
};

int main()
{
	Rational a(1,2);
	Rational b(3,5);
	Rational c = a*b;
	return 0;
}
// 打印结果
Rational construct.......0x61fe00
Rational construct.......0x61fdf8
Rational operator*:
Rational copy construct!: 0x61fdf0
destruct this object!: 0x61fe08
destruct this object!: 0x61fdf0
destruct this object!: 0x61fdf8
destruct this object!: 0x61fe00

在这里插入图片描述

1. operator* 以by value 方式返回一个结果

friend const Rational operator* (const Rational& lhs,const Rational& rhs);

当以 by value 的方式返回其 计算结果(一个对象)时,如果你完全不考虑该对象的 构造和析构成本,那么你就是逃避了专业责任,若非必要,这种代价是不必要付出的。
💚💚 可以看到 这里 const Rational opertor*() 计算结果返回时:有一个构造函数和一个 拷贝构造函数的成本支出。
如何节约这种成本的支出了? 试试,通过 以 by reference 的方式返回 ?

2. operator* 以 by Reference 方式返回一个结果

  1. 如果可以改而传递 reference,就不需要付出代价,但是你也应该记住,所谓 Reference 只是一个名称,代表某个既有对象,在任何时候看到一个 reference 声明式,你应该立刻问问自己:它的另一个名称是什么? 因为它一定是某物的另一个名称。
  2. 以上述 operator* 为例,如果它返回一个 refernece ,那么后者一定指向某个既有的 Rational 对象,这个对象 内含两个 Rational 对象的乘积。
  3. 那么函数创建这个对象的途径有两个:在 stack 空间或在 Heap堆空间,在这里你应该考虑定义一个 局部变量(在 stack 上创建)。

💚💚(一) 如果我们这样创建 :考虑在 栈上构造一个对象

const Rational& operaot*(const Rational& lhs, const Rational& rhs)
{
	Rational result (lhs.n * lhs.n, rhs.n* rhs.n);
	return result;
}
  1. 一方面,我们本来就是要减少构造函数的支出成本,而 result 却必须像任何对象一样由构造函数构造起来
  2. 更严重的是:函数返回一个 reference 指向 result ,但是 这个 result 是一个 local 变量,local 对象在函数退出前被销毁,因此这个 operator* 并未返回 reference 指向的 Rational ,而是指向一个 旧的,从前的 Rational 。这将导致 “无定义行为”的恶地。

💚💚(二):我们考虑在堆上构建一个对象

	friend const Rational& operator*(const Rational& lhs, const Rational& rhs)
	{
		Rational* result = new Rational(*(lhs.n * lhs.n), *(rhs.d* rhs.d));
		return *result;
	}

💚 对于基础不牢的同学,可能会犯如下错误。
在这里插入图片描述

  1. 从上面的打印可以看出:在堆上构造一个Rational对象 ,还是有构造函数和拷贝构造函数以及析构函数的开支花销。
  2. 更为严重的是:你在堆上new 出来的对象,谁应该对此对象实施 delete ? (即没有合理的办法让他们取得 operator* 返回reference背后隐藏的那个指针,这肯定会导致资源泄漏)。

3 定义static Rational 对象

上述两个小节中,不论是在 on-the-stack上或 on-the-heap的做法,都因为对 operator* 返回的结果调用构造函数而受到惩罚,你可能现在想到了一种方法,避免构造函数被调用。
💚💚 让operator* 返回的reference 指向一个被定于函数内部的 static Rational 对象。

    friend const Rational& operator*(const Rational& lhs, const Rational& rhs)
	{
		static Rational* result;
		result = new Rational(lhs.n * lhs.n,rhs.d*rhs.d);
		return *result;
	}
	// 打印结果
Rational construct.......0x61fe08
Rational construct.......0x61fe00
Rational construct.......0xe927c0
Rational copy construct!: 0x61fdf8
destruct this object!: 0x61fdf8
destruct this object!: 0x61fe00
destruct this object!: 0x61fe08

💚💚 那么上面返回 static Rational 对象这种方式有什么问题了?

  1. 多线程安全性的疑虑,这是个显而易见的弱点
  2. 由于 static 对象储存在 全局区,调用者看到永远是 static Rational对象的现值。这个虽然满足目前场景,但是如果你需要比较两个等式结果的话,就会出现问题如 :
bool operator==(const Rational& lhs,const Ration& rhs);
Rational a,b,c,d;
if ((a*b) == (c*d))
{
}
// 表达式永远是 true , 因为
3.operator== 被调用前,operator* 两个表达式每一个都是: 返回 Reference 指向 operaotr* 内部定义的 static Rational
4. 因此 operator== 实际上是要求将:operator* 内的 static Rational 对象值 和operator* 内的 static Rational 对象值进行比较,这个肯定是相等的。

总结

  1. 在需要返回 “一个对象的时候”。你就返回一个新对象,如上述例子,对Rational 的 operator* 而言,就应该返回一个对象
inline const Rational operaotr*(const Rational& lhs, const Rational& rhs)
{
	return Rational(lhs.n*lhs.n, rhs.d*rhs.d);
}
  1. 虽然返回一个对象,可能带来构造和析构的成本,但是C++是允许编译器实施最优化,用以改善代码的效率,因此某些情况下operator* 返回值的构造和析构可被安全的消除。

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

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

相关文章

卷积神经网络的原理、结构和应用

深度学习是一种人工神经网络的应用&#xff0c;其应用范围包括自然语言处理、计算机视觉、语音识别等等。其中&#xff0c;卷积神经网络&#xff08;Convolutional Neural Network&#xff0c;CNN&#xff09;是一种应用广泛的图像识别模型&#xff0c;其用于解决计算机视觉领域…

【Linux】多种环境变量介绍与设置

文章目录 一. linux环境变量介绍1. linux中的环境变量配置文件2. 环境变量加载顺序 二. 操作环境变量1. 读取环境变量envexportecho $PATH 2. 设置环境变量2.1. export PATH&#xff1a;临时的环境变量2.2. 用户的环境变量vim ~/.bashrcvim ~/.bash_profile 2.3. 所有用户的环境…

软件详细设计总复习(二)【太原理工大学】

文章目录 二、结构型模式1. 适配器模式2. 桥接模式3. 组合模式4. 装饰模式5. 外观模式6. 代理模式 二、结构型模式 1. 适配器模式 适配器是用来将两个原本并不兼容的接口能够在一起工作。就像我们的充电线可以让手机接口和插座接口相互适应&#xff0c;完成工作。 课本上的案…

Linux防火墙iptables

文章目录 一.iptables概述二.netfilter/iptables 关系三.四表五链3.1作用3.2四表3.3五链3.4规则表的优先顺序3.5规则链的匹配顺序3.6iptables 命令行配置方法3.8常用管理选项3.9iptables安装 四、操作4.1 增加规则4.2删除规则4.3修改规则4.4查询规则 五、规则匹配5.1通用匹配5.…

IDEA快捷键总结

IDEA快捷键总结 KeyMap使用的是Eclipse 常用快捷键 Ctrl H 全局搜索Shift Shift 搜索源码Ctrl O 查看当前类或接口包含的方法&#xff0c;即自身结构。Ctrl Alt B 选中接口名&#xff0c;查看当前接口的实现类Ctrl Alt V 快速补全Ctrl Alt ↓ 复制当前行到下一行C…

广告让你不自觉地掏钱?消费者行为背后的心理学

一般来说&#xff0c;应该从广告的各个方面提升&#xff0c;比如与目标用户的需求匹配&#xff0c;产品定位&#xff0c;核心卖点&#xff0c;突出重点和价值&#xff0c;不断重复&#xff0c;等等的这些都说的很好&#xff0c;给用户提供了做这件事的足够的动机和理由。 但我…

【组合优化】基于CHHO的QoS感知的web服务组合优化【Matlab代码22#】

文章目录 【可更换其他算法&#xff0c;获取资源请见文章第7节&#xff1a;资源获取】1. Web服务2. QoS感知的Web服务组合3. 改进后的CHHO算法3.1 原始HHO算法3.2 CHHO算法 4. 优化目标5. 部分代码展示6. 仿真结果展示7. 资源获取 【可更换其他算法&#xff0c;获取资源请见文章…

rpc与grpc学习记录

文章目录 1、RPC2、gRPC多线程pythongrpc代码1、安装python需要的库&#xff1a;2、grpc编程步骤3、Demo13.1、编写 .proto文件&#xff0c;定义接口和数据类型3.2、编译 .proto文件生成存根文件3.3、编写服务器端代码&#xff1a;3.4、编写客户端代码&#xff1a;3.5、测试 1、…

docker操作2

docker操作2 文章目录 docker操作2启动新容器配置新的容器后要做的操作进入Docker容器可以显示图片的容器镜像pull 网络镜像 日志停止与删除停止删除删除image报错 在容器和宿主机之间拷贝数据创建命令别名查看docker运行容器的ipdocker image保存与导入保存image导入image 打标…

CMake的应用与实践

CMake 简介 CMake是什么&#xff1f; 全称 Cross Platform Make&#xff0c;起初为了跨平台需求&#xff0c;而后不断完善并广泛使用一款优秀的工程构建工具 特点和优势 开放源代码&#xff0c;具有BSD许可跨平台&#xff0c;支持Linux&#xff0c;Mac和Windows等不同操作系…

【C生万物】 字符串内存函数篇 (上)

欢迎来到 Claffic 的博客 &#x1f49e;&#x1f49e;&#x1f49e; &#x1f449; 专栏&#xff1a;《C生万物 | 先来学C》&#x1f448; 前言&#xff1a; 过了指针这个坎后&#xff0c;下一步就是C语言中关于字符的处理&#xff0c;这一期来讲…

chatgpt赋能Python-python5个一行

Python: 5行代码改变世界 Python是一种高级编程语言&#xff0c;以其简单易学的特性而闻名。Python的发明者Guido van Rossum在1980年代末和1990年代初创造了Python&#xff0c;旨在创建一种语言&#xff0c;既易于理解又易于使用。如今&#xff0c;Python已经成为了最受欢迎的…

【离散数学】陪集和拉格朗日定理编程题

1&#xff1a;编写一个程序能够计算有限群G的子群H的左陪集 输入一个n阶有限群G的二元运算表及相关的子群&#xff0c;输出其左陪集。 &#xff08;注意&#xff1a;按照表头元素顺序计算每个陪集&#xff0c;下图为G的二元运算表示例&#xff09; 样例1&#xff1a; 输入&…

干货 | 利用SPSS进行高级统计分析第三期

Hello&#xff0c;大家好&#xff01; 这里是壹脑云科研圈&#xff0c;我是喵君姐姐~ 在本期中&#xff0c;我们继续为大家介绍如何利用SPSS进行&#xff1a;单因素方差分析、多因素方差分析、重复测量方差分析等。 1. 单因素方差分析【组间实验单一因变量&#xff1b;进行差…

iptables防火墙中的SNAT和DNAT

SNAT的原理和应用 SNAT 应用环境∶局域网主机共享单个公网IP地址接入Internet &#xff08;私有IP不能在Internet中正常路由&#xff09; SNAT原理∶修改数据包的源地址。 SNAT转换前提条件∶ 局域网各主机已正确设置IP地址、子网掩码、默认网关地址Linux网关开启IP路由转发…

游资92科比到底牛在哪里?

昨天一天时间把92科比之前的一个帖子全部看完&#xff0c;从科比对情绪周期的把握来看那简直总结的已经是标准答案了&#xff0c;那么为何92科比公布了答案&#xff0c;还是有很多人“痛苦”的做不到&#xff1f; 这个问题我觉得跟退学炒股是一样的&#xff0c;退学先解决了小…

springcloud-alibaba (03)sentinel下载安装

Sentinel 一&#xff0c;下载安装&#x1f4a0;二&#xff0c;编写脚本&#x1f9ff;三&#xff0c;启动sentinel&#x1f48e;四&#xff0c;Win-访问控制台✨ 在Linux中下载安装Spring Cloud Alibaba Sentinel&#xff0c;可以按照以下步骤进行操作&#xff1a; 一&#xff0…

Python常见面试题. Python中的解包操作及其应用场景

这是个简单的知识点&#xff0c;但有的同学并不理解 unpacking解包 解&#xff0c;对应的是*或者**&#xff0c;也有自动解包之说 包对应的可迭代对象 Python助学大礼包点击跳转获取 目录 一、自动解包二、数据过多的解包*三、星号在函数中的使用四、两个星号的解包五、场…

Lesson13---人工神经网络(2)

13 人工神经网络&#xff08;2&#xff09; 多层神经网络-非线性分类问题多层神经网络的损失函数不是凸函数&#xff0c;很难计算解析解通常采用梯度下降法&#xff0c;得到数据解&#xff0c;梯度下降法可以用来求解函数极值问题 批量梯度下降随机梯度下降小批量梯度下降 1…

[Nacos] Nacos Client获取所有服务和定时更新Client端的注册表 (三)

文章目录 1.Nacos Client获取所有服务1.1 Client如何获取所有服务1.2 Client获取服务方法getServices()详解 2.Nacos定时更新Client端的注册表2.1 Nacos和Eureka定时更新Client端的注册表的区别2.2 Client定时更新本地服务过程2.3 updateServiceNow方法解析2.4 定时更新本地注册…