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

C++ 函数返回值优化

本文中部分内容来自下面的文章,还有一部分来自智谱清言 

C++ 返回值优化_c++ 局部变量返回优化-CSDN博客

elision:省略

copy elision:拷贝省略

RVO (Return Value Optimization):返回值优化

------

我最近也遇到了上面博文中说到的问题:

观察这段代码:

std::vector<int> f() {std::vector<int> v;// do some thing with vreturn v;
}std::vector<int> test = f();

------

在C++中,当返回一个局部对象时,会发生以下事情:

拷贝返回:默认情况下,返回局部对象时,会通过拷贝构造函数创建返回值的副本

这意味着在函数返回后,局部对象被销毁,但是它的副本会被用来初始化调用处的变量

------

在这个例子中,返回局部对象v时,会通过拷贝构造函数创建返回值的副本[假设是temp]

这意味着在函数返回后,局部对象 v 被销毁,但是它的副本temp会被用来初始化调用处的变量test

------

移动语义(C++11及以后版本)

如果编译器支持返回值优化(RVO,Return Value Optimization)或者具名返回值优化(NRVO,Named Return Value Optimization),那么在返回 v 时,编译器可能会直接在调用者作用域构造 test,从而避免了额外的拷贝。

如果 std::vector<int> 支持移动构造函数,编译器还可能会使用移动语义,这意味着 v 的资源(如动态分配的内存)会被移动到 test 中,而不是复制,从而提高效率。

------ 

拷贝返回的话,共调用了1次构造函数,2次拷贝构造函数:

  1. 一次构造函数调用:在函数 f() 内部,std::vector<int> v; 这行代码会调用 std::vector<int> 的默认构造函数来创建局部对象 v

  2. 两次拷贝构造函数调用

    • 第一次是在 return v; 语句执行时,这时会创建一个 v 的副本,用于返回给函数的调用者
    • 第二次是在函数外部接收返回值时,即 std::vector<int> test = f(); 这行代码,这里 test 通过拷贝构造函数使用 f() 返回的 v 的副本[假设是temp]来初始化。

------ 

值得注意的是,现代C++编译器通常会应用返回值优化RVO)或具名返回值优化NRVO),在这种情况下,编译器可能会优化掉不必要的拷贝。

------

如果编译器能够应用这种优化,那么实际上可能只会有一次构造函数调用,即使是在返回局部对象时。在这种情况下,编译器会在调用者作用域直接构造 test,而不是在函数内部创建一个副本[假设是temp]然后拷贝它。

------

例如,如果编译器应用了RVO/NRVO,那么上面的代码在优化后的汇编代码中,可能会直接在 test 的位置构造 std::vector<int>,从而避免了额外的拷贝。

如果 std::vector<int> 支持移动语义(在C++11及以后版本中),编译器还可能会使用移动构造函数来进一步提高效率。

---------

对于上面的例子,通过返回值优化(RVO)或具名返回值优化(NRVO),编译器可以省略掉两次不必要的拷贝构造函数调用,只会调用一次构造函数。

具体来说:

  • RVO(Return Value Optimization):当编译器检测到函数返回的是一个局部对象,并且该局部对象是直接返回的没有经过任何中间步骤),编译器可能会直接在调用者的栈上构造这个对象,而不是在函数内部构造然后再拷贝。

  • NRVO(Named Return Value Optimization):这是RVO的一种特殊情况,当返回的局部对象有名字时,编译器可能会使用NRVO。

------

对于上面的示例,我猜:会使用NRVO:

编译器检测到函数返回的是局部对象v,并且该局部对象是直接返回的没有经过任何中间步骤),编译器会直接在调用者的栈上构造对象test,而不是在函数内部构造v然后再拷贝到temp再拷贝到test

------

如果没有应用RVO/NRVO,但编译器支持移动语义(C++11及以后版本),

并且 std::vector<int> 支持移动构造函数,那么编译器可能会使用移动构造函数来代替拷贝构造函数,这样仍然可以避免拷贝,但会调用一次移动构造函数。在这种情况下,总共会有一次构造函数调用一次移动构造函数调用

------

移动构造函数在何时被调用?

当对象即将被销毁,并且其资源可以被移动到另一个对象时

在函数 f() 返回局部对象 v 时,局部对象 v 将要离开其作用域并被销毁

如果此时 v 的资源(如动态分配的内存)可以被移动而不是复制,那么编译器会调用移动构造函数来将 v 的资源移动到将要接收返回值的对象test中。

------

总结:

  • 如果编译器应用了RVO/NRVO:总共一次构造函数调用。
  • 如果编译器没有应用RVO/NRVO,但使用了移动语义:总共一次构造函数调用和一次移动构造函数调用。
  • 如果编译器既没有应用RVO/NRVO,也没有使用移动语义:总共一次构造函数调用和两次拷贝构造函数调用。不过,在现代编译器中,这种情况不太可能发生,因为它们通常会利用RVO/NRVO或移动语义来优化代码。

------

http://www.lryc.cn/news/487818.html

相关文章:

  • c++源码阅读__ThreadPool__正文阅读
  • 关于ES的查询
  • 数据结构初识
  • 保存数据到Oracle时报错ORA-17004: 列类型无效: 1111
  • Excel——宏教程(1)
  • 论文浅尝 | MindMap:知识图谱提示激发大型语言模型中的思维图(ACL2024)
  • 第6章:TDengine 标签索引和删除数据
  • 【微软:多模态基础模型】(5)多模态大模型:通过LLM训练
  • 海外带云仓多语言商城源码,多语言多商家云仓一键代发商城
  • android:taskAffinity 对Activity退出时跳转的影响
  • Apache Dolphinscheduler数据质量源码分析
  • solana链上智能合约开发案例一则
  • 使用 PyTorch 实现 ZFNet 进行 MNIST 图像分类
  • 车轮上的科技:Spring Boot汽车新闻集散地
  • IDEA2023 SpringBoot整合Web开发(二)
  • 国产三维CAD 2025新动向:推进MBD模式,联通企业设计-制造数据
  • ubuntu 之 安装mysql8
  • Flink Lookup Join(维表 Join)
  • Elasticsearch retrievers 通常与 Elasticsearch 8.16.0 一起正式发布!
  • 【并发模式】Go 常见并发模式实现Runner、Pool、Work
  • 【前端知识】Javascript前端框架Vue入门
  • Springboot3.3.5 启动流程之 Bean创建流程
  • golang反射函数注册
  • 【Spring】Bean
  • 深入解析TK技术下视频音频不同步的成因与解决方案
  • 为什么要使用Ansible实现Linux管理自动化?
  • Android:任意层级树形控件(有效果图和Demo示例)
  • C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
  • C++---类型转换
  • CSS基础学习练习题