许多旧的C++代码库显示有点害怕复制对象的模式。幸运的是,由于所谓的“return value optimization”(rvo),我们可以不复制而“复制”。

RVO是几乎所有C++编译器的一个长期特性。考虑下面的C++ 98代码,它有一个复制构造函数和一个赋值操作符。这些功能非常昂贵,开发人员让它们每次使用时都打印一条消息:

class SomeBigObject {public:SomeBigObject() { ... }SomeBigObject(const SomeBigObject& s) {printf("Expensive copy …\n", …);…}SomeBigObject& operator=(const SomeBigObject& s) {printf("Expensive assignment …\n", …);…return *this;}~SomeBigObject() { ... }…
};

请注意,我们有意避免在这里讨论移动操作。更多信息请参见TotW #77

如果这个类有如下工厂方法,您会在恐惧中退缩吗?

static SomeBigObject SomeBigObjectFactory(...) {SomeBigObject local;...return local;
}

看起来效率很低,不是吗?如果我们运行以下程序会发生什么?

SomeBigObject obj = SomeBigObject::SomeBigObjectFactory(...);

简单回答:您可能期望至少创建两个对象:从被调用函数返回的对象和调用函数中的对象。两者都是副本,因此程序打印两条关于昂贵操作的消息。现实世界的答案:没有消息被打印出来——因为从未调用复制构造函数和赋值运算符!

怎么会这样?许多C++程序员编写“高效代码”,创建一个对象并将该对象的地址传递给一个函数,该函数使用该指针或引用对原始对象进行操作。好吧,在下面描述的情况下,编译器可以将这种“低效拷贝”转换为“高效代码”

当编译器在调用函数中看到一个变量(将从返回值构造)和一个被调用函数中的变量(将被返回)时,它意识到不需要这两个变量。在封面下,编译器将调用函数变量的地址传递给被调用函数。

引用C++ 98标准,“每当使用复制构造函数复制临时类对象时,允许实现将原始和副本视为两种不同的引用同一对象的方式,而不执行复制,即使类复制构造函数或析构函数有副作用。对于具有类返回类型的函数,如果返回语句中的表达式是本地对象的名称……允许一个实现省略创建临时对象来保存函数返回值……((第12.8节[class.copy]),C++ 98标准的第15段。C++ 11标准在第12.8节第31段中有相似的语言,但它更复杂)

担心“允许”不是一个很强的承诺?幸运的是,所有现代C++编译器默认情况下都执行RVO,即使在调试版本中,甚至对于非内联函数也是如此。

如何确保编译器执行rvo?

被调用函数应为返回值定义一个变量

SomeBigObject SomeBigObject::SomeBigObjectFactory(...) {SomeBigObject local;…return local;
}

调用函数应将返回值赋给新变量:

// No message about expensive operations:
SomeBigObject obj = SomeBigObject::SomeBigObjectFactory(...);

就这样!

如果调用函数重用现有变量来存储返回值,则编译器无法执行rvo(尽管在这种情况下,移动语义将适用于启用移动的类型):

// RVO won’t happen here; prints message "Expensive assignment ...":
obj = SomeBigObject::SomeBigObjectFactory(s2);

如果被调用函数使用多个变量作为返回值,则编译器也无法执行rvo:

// RVO won’t happen here:
static SomeBigObject NonRvoFactory(...) {SomeBigObject object1, object2;object1.DoSomethingWith(...);object2.DoSomethingWith(...);if (flag) {return object1;} else {return object2;}
}

但是,如果被调用的函数使用一个变量并在多个地方返回它,则可以:

// RVO will happen here:
SomeBigObject local;
if (...) {local.DoSomethingWith(...);return local;
} else {local.DoSomethingWith(...);return local;
}

这可能是你所需要知道的所有关于RVO

还有一件事:Temporaries

RVO使用临时对象,而不仅仅是命名变量。当被调用函数返回临时对象时,可以从rvo中获益:

// RVO works here:
SomeBigObject SomeBigObject::ReturnsTempFactory(...) {return SomeBigObject::SomeBigObjectFactory(...);
}

当调用函数立即使用返回值(存储在临时对象中)时,还可以从rvo中获益

// No message about expensive operations:
EXPECT_EQ(SomeBigObject::SomeBigObjectFactory(...).Name(), s);

最后一个注意事项:如果您的代码需要进行复制,那么就进行复制,不管这些复制是否可以被优化掉。不要为了效率而牺牲正确性。

Abseil之Return Policy相关推荐

  1. Flurl使用Polly实现重试Policy

    ❝ 在使用Flurl作为HttpClient向Server请求时,由于网络或者其它一些原因导致请求会有失败的情况,比如HttpStatusCode.NotFound.HttpStatusCode.Se ...

  2. Policy Iteration与Value Iteration

    gym环境:FrozenLake-v0:http://gym.openai.com/envs/FrozenLake-v0/ 代码来自:周博磊老师的GitHub:https://github.com/c ...

  3. 强化学习(四) - 蒙特卡洛方法(Monte Carlo Methods)及实例

    强化学习(四) - 蒙特卡洛方法(Monte Carlo Methods)及实例 4. 蒙特卡洛方法 4.1 蒙特卡洛预测 例4.1:Blackjack(21点) 4.2 动作价值的蒙特卡洛估计 4. ...

  4. 强化学习(三) - Gym库介绍和使用,Markov决策程序实例,动态规划决策实例

    强化学习(三) - Gym库介绍和使用,Markov决策程序实例,动态规划决策实例 1. 引言 在这个部分补充之前马尔科夫决策和动态规划部分的代码.在以后的内容我会把相关代码都附到相关内容的后面.本部 ...

  5. 深入理解 Java 注解

    本文内容基于 JDK8.注解是 JDK5 引入的,后续 JDK 版本扩展了一些内容,本文中没有明确指明版本的注解都是 JDK5 就已经支持的注解. :notebook: 本文已归档到:「blog」 : ...

  6. linux 进程优先级 之设置实时进程 (另一种方式是设置nice值)【转】

    转自:https://www.cnblogs.com/jkred369/p/6731353.html Linux内核的三种调度策略: 1,SCHED_OTHER 分时调度策略, 2,SCHED_FIF ...

  7. Spring-Retry重试实现原理

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 作者 | Alben 来源 | http://r6d.c ...

  8. tc-core-library-js学习笔记

    1.组件概览 middleware jwtAuthenticator . ./lib/middleware/jwtAuthenticator logger ./lib/middleware/logge ...

  9. 浅析Linux线程调度

    在Linux中,线程是由进程来实现,线程就是轻量级进程( lightweight process ),因此在Linux中,线程的调度是按照进程的调度方式来进行调度的,也就是说线程是调度单元.Linux ...

最新文章

  1. get_date.sh
  2. C/C++面试题目集锦
  3. CCNA笔记-交换机安全
  4. jquery.form.js插件中ajaxSubmit提交在jquery1.4版本中的应用
  5. equal_range
  6. CCF201909-1 小明种苹果
  7. 最新大润发优鲜小程序逆向分析
  8. python图片马赛克_Python实现PS滤镜中马赛克效果示例
  9. python利用集合的无重复性_利用Python程序完成ABAQUS中的一些重复性操作
  10. java调用数据库存储过程的接口是_JAVA调用数据库存储过程
  11. 开源项目bootdo的实战开发笔记
  12. 杭电计算机2010年笔试真题详解
  13. python逻辑运算符例子_python运算符-实战中常用的三个逻辑运算符使用实例
  14. English音标(全)与单词家园
  15. Navicat Premium 历史版本地址
  16. WPS 手动去除广告
  17. python编写一个函数判断一个数是否为素数是则返回yes_编写函数,判断一个整数是否为素数,并编写主程序调用该函数。_学小易找答案...
  18. dodo:人脸识别方法个人见解 (zz)
  19. HAC集群修改管理员用户密码
  20. 深度解读BN、LN、WN、CN

热门文章

  1. 功能丰富的Perl:用Perl读写Excel文件
  2. java获取反射机制的三种方式
  3. mysql 查看数据表大小_关于MySQL 查询表数据大小的总结
  4. .describe() python_Python实现ARMA模型
  5. 概要设计说明书任务分配
  6. 【原创】大数据基础之Spark(1)Spark Submit即Spark任务提交过程
  7. 函数强化练习2(py引入模块、包)
  8. 在Nginx服务器上用Jenkins发布Vue/React代码的步骤
  9. 关于浏览器Ajax请求Type为Request Payload而不是Form Data
  10. 【Linux】Windows Ubuntu 双系统开机选择界面设置