当前位置: 首页 > news >正文

C++协助完成返回值优化

   一个返回对象的函数很难有较高的效率,因为传值返回会导致调用对象内的构造和析构函数(参见条款M19),这种调用是不能避免的。问题很简单:一个函数要么为了保证正确的行为而返回对象要么就不这么做。如果它返回了对象,就没有办法摆脱被返回的对象。就说到这。

考虑rational(有理数)类的成员函数operator*:

class Rational {

public:

  Rational(int numerator = 0, int denominator = 1);

  ...

  int numerator() const;

  int denominator() const;

};

// 有关为什么返回值是const的解释,参见条款M6,

const Rational operator*(const Rational& lhs,

                         const Rational& rhs);

   甚至不用看operator*的代码,我们就知道它肯定要返回一个对象,因为它返回的是两个任意数字的计算结果。这些结果是任意的数字。operator*如何能避免建立新对象来容纳它们的计算结果呢?这是不可能的,所以它必须得建立新对象并返回它。不过C++程序员仍然花费大量的精力寻找传说中的方法,能够去除传值返回的对象。

   有时人们会返回指针,从而导致这种滑稽的句法:

// 一种不合理的避免返回对象的方法

const Rational * operator*(const Rational& lhs,

                           const Rational& rhs);

Rational a = 10;

Rational b(1, 2);

Rational c = *(a * b);             //你觉得这样很“正常”么?

   它也引发出一个问题。调用者应该删除函数返回对象的指针么?答案通常是肯定的,并且通常会导致资源泄漏。

   其它一些开发人员会返回引用。这种方法能产生可接受的句法,

//一种危险的(和不正确的)方法,用来避免返回对象

const Rational& operator*(const Rational& lhs,

                          const Rational& rhs);

Rational a = 10;

Rational b(1, 2);

Rational c = a * b;                          // 看上去很合理

   但是函数不能被正确地实现。一种尝试的方法是这样的:

// 另一种危险的方法 (和不正确的)方法,用来

// 避免返回对象

const Rational& operator*(const Rational& lhs,

                          const Rational& rhs)

{

  Rational result(lhs.numerator() * rhs.numerator(),

                  lhs.denominator() * rhs.denominator());

  return result;

}

   这个函数返回的引用,其指向的对象已经不存在了。它返回的是一个指向局部对象result的引用,当operator* 退出时result被自动释放。返回指向已被释放的对象的引用,这样的引用绝对不能使用。

   相信我:一些函数(operator*也在其中)必须要返回对象。这就是它们的运行方法。不要与其对抗,你不会赢的。

   你消除传值返回的对象的努力不会获得胜利。这是一场错误的战争。从效率的观点来看,你不应该关心函数返回的对象,你仅仅应该关心对象的开销。你所应该关心的是把你的努力引导到寻找减少返回对象的开销上来,而不是去消除对象本身(我们现在认识到这种寻求是无用的)。如果没有与这些对象相关的开销,谁还会关心有多少对象被建立呢?

   以某种方法返回对象,能让编译器消除临时对象的开销,这样编写函数通常是很普遍的。这种技巧是返回constructor argument而不是直接返回对象,你可以这样做:

// 一种高效和正确的方法,用来实现

// 返回对象的函数

const Rational operator*(const Rational& lhs,

                         const Rational& rhs)

{

  return Rational(lhs.numerator() * rhs.numerator(),

                  lhs.denominator() * rhs.denominator());

}

   仔细观察被返回的表达式。它看上去好象正在调用Rational的构造函数,实际上确是这样。你通过这个表达式建立一个临时的Rational对象,

Rational(lhs.numerator() * rhs.numerator(),

         lhs.denominator() * rhs.denominator());

   并且这是一个临时对象,函数把它拷贝给函数的返回值。

   返回constructor argument而不出现局部对象,这种方法还会给你带来很多开销,因为你仍旧必须为在函数内临时对象的构造和释放而付出代价,你仍旧必须为函数返回对象的构造和释放而付出代价。但是你已经获得了好处。C++规则允许编译器优化不出现的临时对象(temporary objects out of existence)。因此如果你在如下的环境里调用operator*:

Rational a = 10;

Rational b(1, 2);

Rational c = a * b;                          // 在这里调用operator*

   编译器就会被允许消除在operator*内的临时变量和operator*返回的临时变量。它们能在为目标c分配的内存里构造return表达式定义的对象。如果你的编译器这样去做,调用operator*的临时对象的开销就是零:没有建立临时对象。你的代价就是调用一个构造函数――建立c时调用的构造函数。而且你不能比这做得更好了,因为c是命名对象,命名对象不能被消除。不过你还可以通过把函数声明为inline来消除operator*的调用开销:

// the most efficient way to write a function returning

// an object

inline const Rational operator*(const Rational& lhs,

                                const Rational& rhs)

{

  return Rational(lhs.numerator() * rhs.numerator(),

                  lhs.denominator() * rhs.denominator());

}

   “好,不错”,你嘀咕地说,“优化,谁关心编译器能做什么?我想知道它们确实做了什么,Does any of this nonsense work with real compilers?” It does。这种特殊的优化――通过使用函数的return 位置(或者在函数被调用位置用一个对象来替代)来消除局部临时对象――是众所周知的和被普遍实现的。它甚至还有一个名字:返回值优化(return value optimization)(WQ加注:在《深度探索C++物件模型》中有更多更详细的讲述,它叫之为named return value optimization。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 【python学习】Python中的 `json.dump` 和 `json.dumps` 的区别和用法解析
  • 深度学习入门(一):感知机与输入数据
  • 【吊打面试官系列-Elasticsearch面试题】详细描述一下 Elasticsearch 搜索的过程?
  • 【算法】贪心算法
  • 大话C语言:第36篇 枚举​
  • 【算法速刷(5/100)】LeetCode —— 20.有效的括号
  • Docker基础知识大全
  • 63 epoll服务器 (ET模式)
  • 【Linux】匿名管道|命名管道|pipe|mkfifo|管道原理|通信分类|管道的特征和情况
  • SQL二次注入
  • xtrabackup搭建MySQL 8.0 主从复制
  • Git 如何提交代码
  • 大话C语言:第37篇 联合体
  • 声明式UI语法
  • 使用nginxproxymanager管理nginx
  • [译] React v16.8: 含有Hooks的版本
  • Android开源项目规范总结
  • Angular2开发踩坑系列-生产环境编译
  • gulp 教程
  • HTML-表单
  • IDEA 插件开发入门教程
  • java第三方包学习之lombok
  • Java新版本的开发已正式进入轨道,版本号18.3
  • Nodejs和JavaWeb协助开发
  • Redis字符串类型内部编码剖析
  • spark本地环境的搭建到运行第一个spark程序
  • Vue实战(四)登录/注册页的实现
  • vue中实现单选
  • XML已死 ?
  • 从地狱到天堂,Node 回调向 async/await 转变
  • 给Prometheus造假数据的方法
  • 基于OpenResty的Lua Web框架lor0.0.2预览版发布
  • 聊聊directory traversal attack
  • 前端临床手札——文件上传
  • 前端性能优化——回流与重绘
  • 如何正确配置 Ubuntu 14.04 服务器?
  • 设计模式走一遍---观察者模式
  • 十年未变!安全,谁之责?(下)
  • 算法-图和图算法
  • 微服务核心架构梳理
  • 原生JS动态加载JS、CSS文件及代码脚本
  • UI设计初学者应该如何入门?
  • 哈罗单车融资几十亿元,蚂蚁金服与春华资本加持 ...
  • ​【经验分享】微机原理、指令判断、判断指令是否正确判断指令是否正确​
  • ​2021半年盘点,不想你错过的重磅新书
  • ​软考-高级-系统架构设计师教程(清华第2版)【第9章 软件可靠性基础知识(P320~344)-思维导图】​
  • ###51单片机学习(1)-----单片机烧录软件的使用,以及如何建立一个工程项目
  • #我与Java虚拟机的故事#连载14:挑战高薪面试必看
  • (145)光线追踪距离场柔和阴影
  • (bean配置类的注解开发)学习Spring的第十三天
  • (k8s)Kubernetes 从0到1容器编排之旅
  • (Mac上)使用Python进行matplotlib 画图时,中文显示不出来
  • (PHP)设置修改 Apache 文件根目录 (Document Root)(转帖)
  • (void) (_x == _y)的作用
  • (阿里云在线播放)基于SpringBoot+Vue前后端分离的在线教育平台项目