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

c++17 对于临时对象作为右值的优化

C++17 对临时对象作为右值的优化主要通过以下机制实现:

一、强制拷贝省略(Guaranteed Copy Elisation)

  1. 纯右值的直接构造
    返回临时对象时,直接在调用方内存空间构造目标对象,无需调用拷贝/移动构造函数。

    纯右值的定义与特性‌
            纯右值表示临时对象或字面量,属于无法取地址的短暂值。
            在C++17前,纯右值返回需通过临时对象传递,可能触发拷贝/移动构造;C++17后,纯右值‌直接在目标地址构造‌,无需中间步骤

         在变量初始化时,若初始值是纯右值(如函数返回的临时对象),则直接构造到目标变量中。

struct Widget {Widget(int) { std::cout << "构造\n"; }Widget(const Widget&) = delete;  // 显式删除拷贝构造函数
};Widget func() { return Widget(42);  // 纯右值:直接构造到调用方内存
}int main() {Widget w = func();  // 直接构造到w的内存,无拷贝/移动
}

输出

构造 

只会调用一次构造函数,不会调用拷贝/移动构造函数,直接构造到w的内存,无拷贝/移动

二、临时物化(Temporary Materialization)    

一、临时物化的定义与触发条件

  1. 定义
    临时物化指纯右值(如字面量、函数返回的非引用对象)在需要实际存储时,隐式转换为将亡值(xvalue)的过程。

  2. 触发场景

    • 绑定到右值引用‌:如 int&& r = 42;,字面量 42(纯右值)需物化为临时对象以绑定到 r
    • 访问成员或子对象‌:如 std::string().c_str()std::string() 需物化为临时对象才能调用 c_str()
    • 初始化非类类型对象‌:如 void f(int&&); f(10);,字面量 10 需物化为 xvalue 以匹配参数类型。

二、右值转换规则与移动语义

  1. 纯右值 → 将亡值
    纯右值物化后成为 xvalue,此时允许移动语义介入(而非强制拷贝)。
    示例‌:

    std::string createString() { return "hello"; }
    std::string s = createString();  
    // C++17 可能直接构造 `s`(无临时对象),否则移动临时对象
    
  2. 移动语义的触发
    xvalue 可被右值引用绑定,从而调用移动构造函数或移动赋值运算符。
    示例‌:

    std::vector<int> v1 = getVector(); 
    // getVector() 返回纯右值,物化为 xvalue 后移动构造 `v1`
    std::vector<int> v2 = std::move(v1); 
    // std::move(v1) 生成 xvalue,移动构造 
    

三、C++17 的关键优化

  1. 延迟物化
    纯右值仅在必要时物化,避免冗余临时对象。例如 auto x = S{}; 直接初始化 x,无需中间临时对象。

  2. 拷贝省略(Copy Elision)
    当纯右值直接初始化对象时(如 S s = S{};),编译器可绕过物化直接构造目标对象(即 RVO/NRVO)。


四、典型示例分析

  1. 字面量的物化

    int&& r = 42;  // 字面量 `42` 物化为 xvalue,绑定到右值引用 `r`
  2. 函数返回值的优化

    std::vector<int> getVec() { return {1, 2, 3}; }
    std::vector<int> v = getVec();  // 可能直接构造 `v`(无物化),或移动临时对象
    
  3. 移动语义的显式使用

    std::string s1 = "temp";
    std::string s2 = std::move(s1);  // std::move(s1) 生成 xvalue,移动赋值给 `s2`


五、总结

  • 临时物化‌是纯右值转换为可操作对象的桥梁,仅在需要时触发。
  • 右值转换规则‌通过将纯右值提升为 xvalue,使移动语义高效介入。
  • C++17 的优化(如延迟物化、拷贝省略)显著减少了冗余拷贝

三、结构化绑定优化

auto&& [a,b] = std::make_pair(1, 2.0); // 临时对象生命周期延长至整个作用域 

结构化绑定声明中,临时对象的生命周期被完整保留,避免悬垂引用问题。

四、移动语义增强

  1. STL 容器优化
    标准库容器(如 vectorstring)在扩容时,对元素优先使用移动而非拷贝操作。

    std::vector<std::string> vec; vec.push_back("temporary"); // 临时string触发移动构造 
  2. 返回值链式优化
    嵌套函数返回的临时对象,可通过链式移动避免多次拷贝:

    std::string process() { return std::string("data").append("_processed"); } 
    // 中间临时对象直接移动,无拷贝开销 

优化效果对比(C++11 vs C++17)

场景C++11C++17
返回不可拷贝对象编译错误允许构造
构造函数副作用可能执行拷贝强制省略拷贝
嵌套临时对象传递多次移动单次构造

这些改进使得 C++17 在处理临时对象时性能更接近理论最优值,同时减少了开发者手动优化的工作量

对比示例说明

示例

#include <iostream>
#include<utility>
class myComplex
{
private:double m_real{0.0};double m_imag{0.0};
public:explicit myComplex(const double r=0.0,const double i=0.0):m_real(r),m_imag(i){std::cout<<"usd construct\n";}myComplex(const myComplex&)=delete;myComplex(myComplex&& )=delete;myComplex& operator=(myComplex&&) =delete;myComplex& operator=(const myComplex&) =delete;myComplex operator+(const myComplex& myC) const {return myComplex(m_real + myC.m_real, m_imag + myC.m_imag);}myComplex& operator+=(const myComplex& mPc){m_real = mPc.m_real;m_imag = mPc.m_imag;return *this;}void myPrintf()const {std::cout<<"real = "<<m_real<<" imag = "<<m_imag<<std::endl;}
};int main()
{   std::cout<<__cplusplus<<std::endl;myComplex c1(1,3);myComplex c2(2,5);myComplex c3 = c1+c2;c3.myPrintf();std::cout<<"end \n";return 0;
}

编译运行 c++17

上述示例中 禁用了拷贝构造、拷贝赋值、移动构造和移动赋值,但 myComplex c3 = c1 + c2; 仍然可以编译执行

关键原因:返回值优化(RVO)(Return Value Optimization

  1. 表达式解析
    c1 + c2 会调用 operator+,它返回一个临时对象(prvalue)。理论上,这个临时对象需要通过拷贝/移动构造来初始化 ,但由于您禁用了这些操作,按理说应该报错。

  2. RVO的介入
    现代C++编译器(特别是启用优化时)会直接‌c3 的内存位置上构造返回值‌,完全跳过拷贝/移动操作。这是标准允许的优化(C++17起强制要求)。

  3. C++17的强制RVO
    从C++17开始,对于纯右值(prvalue)的初始化,编译器‌必须‌省略拷贝/移动操作,即使这些操作被删除或不可访问。

编译c++14 或者c++11 报错

原因是c++14无优化 需要可用的拷贝/移动构造函数,不会强制拷贝省略。

修改示例

#include <iostream>
#include<utility>
class myComplex
{
private:double m_real{0.0};double m_imag{0.0};
public:explicit myComplex(const double r=0.0,const double i=0.0):m_real(r),m_imag(i){std::cout<<"usd construct\n";}// 拷贝构造函数myComplex(const myComplex& other) : m_real(other.m_real), m_imag(other.m_imag) {std::cout << "Copy constructor called\n";}// 移动构造函数myComplex(myComplex&& other) noexcept : m_real(std::exchange(other.m_real, 0)), m_imag(std::exchange(other.m_imag, 0)) {std::cout << "Move constructor called\n";}// 拷贝赋值运算符myComplex& operator=(const myComplex& other) {if (this != &other) {  // 防止自赋值m_real = other.m_real;m_imag = other.m_imag;}std::cout << "Copy assignment called\n";return *this;}// 移动赋值运算符myComplex& operator=(myComplex&& other) noexcept {if (this != &other) {  // 防止自赋值//...}std::cout << "Move assignment called\n";return *this;}myComplex operator+(const myComplex& myC) const {   std::cout << "operator+ called\n";return myComplex(m_real + myC.m_real, m_imag + myC.m_imag);}myComplex& operator+=(const myComplex& mPc){   std::cout << "operator+= called\n";m_real = mPc.m_real;m_imag = mPc.m_imag;return *this;}void myPrintf()const {std::cout<<"real = "<<m_real<<" imag = "<<m_imag<<std::endl;}// myComplex(const myComplex&)=delete;// myComplex(myComplex&& )=delete;// myComplex& operator=(myComplex&&) =delete;// myComplex& operator=(const myComplex&) =delete;
};int main()
{   std::cout<<__cplusplus<<std::endl;myComplex c1(1,3);myComplex c2(2,5);myComplex c3 = c1+c2;c3.myPrintf();std::cout<<"end \n";return 0;
}

补充

NRVO

在 C++ 中,‌NRVO‌(Named Return Value Optimization,具名返回值优化)是编译器对函数返回具名局部对象时的一种优化技术,属于 RVO(返回值优化)的特殊情况。其核心目的是消除返回对象时的拷贝或移动开销,直接在调用方的内存位置构造目标对象。


NRVO 的核心特性

  1. 适用场景

    • 函数返回一个‌具名的局部对象‌(非临时对象或全局变量)。
    • 示例:
      std::string createString() {std::string s = "Hello";  // 具名局部对象return s;                 // NRVO 可能优化
      }
      
  2. 优化效果

    • 跳过临时对象的构造和拷贝/移动操作,直接在调用方(如 main 函数中的接收变量)内存中构造返回值。
    • 对比未优化代码(可能触发拷贝/移动构造函数),性能显著提升。
  3. 与 RVO 的区别

    • RVO‌ 优化匿名临时对象(如 return std::string("Hello");),而 ‌NRVO‌ 优化具名对。
    • NRVO 的实现复杂度更高,编译器支持程度可能因版本而异。

NRVO 的触发条件

  • 返回的局部对象类型必须与函数返回类型严格匹配(无类型转换)。
  • 返回语句必须是局部对象的名称(如 return s;),不能是表达式或条件分支。
  • C++17 起,部分场景(如 URVO)强制优化,但 NRVO 仍为编译器可选优化。

示例与注意事项

// 触发 NRVO
myComplex func() {myComplex obj(1.0, 2.0);  // 具名对象return obj;               // 直接构造到调用方
}// 不触发 NRVO 的情况
myComplex func(bool flag) {myComplex a(1.0, 2.0), b(3.0, 4.0);return flag ? a : b;      // 条件分支,无法优化:ml-citation{ref="6,7" data="citationList"}
}

总结

NRVO 通过消除不必要的拷贝/移动操作提升性能,但需依赖编译器的实现和代码的规范性。在资源管理类(如智能指针、容器)中,合理利用 NRVO 可显著减少动态内存操作的开销

RVO/NRVO 优化 c++11

C++11 标准明确支持 ‌RVO(返回值优化)‌ 和 ‌NRVO(命名返回值优化)‌,它们是编译器在特定场景下消除临时对象拷贝的关键优化技术。


1. ‌RVO/NRVO 在 C++11 中的支持

  • RVO‌:当函数返回‌匿名临时对象‌时,编译器可直接在调用者的存储空间构造该对象,避免拷贝/移动。
    示例:
    myComplex operator+(...) { return myComplex(...); } // 匿名临时对象触发 RVO 

  • NRVO‌:当函数返回‌命名局部对象‌时,编译器可能直接将该对象替换为返回值,省略拷贝。
    示例:
    myComplex func() { myComplex tmp; // 命名对象 return tmp; // 可能触发 NRVO } 


2. ‌C++11 与 C++17 的差异

  • C++11‌:RVO/NRVO 是‌可选优化‌,编译器可根据实现决定是否应用。
  • C++17‌:RVO 被‌强制要求‌(拷贝省略成为标准行为),但 NRVO 仍依赖编译器实现。

3. ‌优化条件与局限性

  • 触发条件‌:
    • 返回值类型与函数声明类型一致。
    • 返回的局部对象‌非函数参数‌且‌非全局变量‌。
  • 可能失效的场景‌:
    • 返回语句包含条件分支(如 if 返回不同对象)。
    • 对象具有移动构造函数(优先调用移动而非优化)。

4. ‌验证优化效果

  • 通过对比编译输出(-O0-O2)或检查汇编代码:
    g++ -O0 -fno-elide-constructors -S test.cpp 
    # 禁用优化查看完整流程 g++ -O2 -S test.cpp # 启用优化观察省略效果 

    示例

#include <iostream>
#include<utility>
class myComplex
{
private:double m_real{0.0};double m_imag{0.0};
public:explicit myComplex(const double r=0.0,const double i=0.0):m_real(r),m_imag(i){std::cout<<"usd construct\n";}// 拷贝构造函数myComplex(const myComplex& other) : m_real(other.m_real), m_imag(other.m_imag) {std::cout << "Copy constructor called\n";}// 移动构造函数myComplex(myComplex&& other) noexcept {   //......std::cout << "Move constructor called\n";}// 拷贝赋值运算符myComplex& operator=(const myComplex& other) {if (this != &other) {  // 防止自赋值m_real = other.m_real;m_imag = other.m_imag;}std::cout << "Copy assignment called\n";return *this;}// 移动赋值运算符myComplex& operator=(myComplex&& other) noexcept {if (this != &other) {  // 防止自赋值//...}std::cout << "Move assignment called\n";return *this;}myComplex operator+(const myComplex& myC) const {   std::cout << "operator+ called\n";return myComplex(m_real + myC.m_real, m_imag + myC.m_imag);}myComplex& operator+=(const myComplex& mPc){   std::cout << "operator+= called\n";m_real = mPc.m_real;m_imag = mPc.m_imag;return *this;}void myPrintf()const {std::cout<<"real = "<<m_real<<" imag = "<<m_imag<<std::endl;}// myComplex(const myComplex&)=delete;// myComplex(myComplex&& )=delete;// myComplex& operator=(myComplex&&) =delete;// myComplex& operator=(const myComplex&) =delete;
};int main()
{   std::cout<<__cplusplus<<std::endl;myComplex c1(1,3);myComplex c2(2,5);myComplex c3 = c1+c2;c3.myPrintf();std::cout<<"end \n";return 0;
}

    编译输出

代码调用了 ‌两次移动构造函数‌,原因如下:


1. 临时对象的构造与传递过程

当禁用 RVO/NRVO(-fno-elide-constructors)时,c1 + c2 的返回值处理会经历以下步骤15:

  1. operator+ 内部构造临时对象
    return myComplex(m_real + myC.m_real, m_imag + myC.m_imag); // 构造第一个临时对象(输出 `usd construct`) 
  2. 第一次移动构造
    operator+ 返回的临时对象移动到 ‌函数返回值存储区‌(编译器生成的中间临时对象),触发第一次 Move constructor called
  3. 第二次移动构造
    将中间临时对象移动到 c3,触发第二次 Move constructor called

2. 代码行为分解

myComplex c3 = c1 + c2; 

具体执行流程:

步骤行为输出
1operator+ 内联构造匿名临时对象usd construct
2匿名临时对象 → 返回值存储区(移动构造)Move constructor called
3返回值存储区 → c3(移动构造)Move constructor called

总结

特性C++11 支持情况说明
RVO支持(可选优化)适用于匿名临时对象15
NRVO支持(依赖编译器)适用于命名局部对象68
强制优化C++17 起对 RVO 强制要求

NRVO 仍非强制8

c++版本查看

#include <iostream>int main() {std::cout << __cplusplus << std::endl;return 0;
}

199711 用于 C++98,

201103 用于 C++11

201402 用于 C++14

201703 用于 c++17

c++ 默认的函数

class MyClass {
public:// 默认构造函数MyClass() = default;// 默认拷贝构造函数MyClass(const MyClass&) = default;// 默认移动构造函数MyClass(MyClass&&) noexcept = default;// 默认拷贝赋值运算符MyClass& operator=(const MyClass&) = default;// 默认移动赋值运算符MyClass& operator=(MyClass&&) noexcept = default;// 析构函数~MyClass() = default;private:int data;std::string str;
};

http://www.xdnf.cn/news/184789.html

相关文章:

  • MRI学习笔记-conjunction analysis
  • Linux——线程(2)线程互斥(锁)
  • 机器学习 | 基于回归模型的交通需求预测案例分析及代码示例
  • 日本IT|UIUX主要的工作都是哪些?及职业前景
  • 【每日随笔】文化属性 ② ( 高维度信息处理 | 强者思维形成 | 认知重构 | 资源捕获 | 进化路径 )
  • LangChain构建大模型应用之RAG
  • 使用ROS实现多机通讯
  • 线上查询车辆出险记录:快速掌握事故情况!
  • 大模型API密钥的环境变量配置(大模型API KEY管理)(将密钥存储在环境变量)(python-dotenv)(密钥管理)
  • 数据结构(七)---链式栈
  • AI看论文自动生成代码库:Paper2Code如何革新科研复现?
  • 函数式链表:Python编程的非常规 “链” 接
  • QT6 源(53)篇三:存储 c 语言字符串的类 QByteArray 的使用举例,
  • 移除生产环境所有console.log
  • 给视频自动打字幕:从Humanoid-X、UH-1到首个人形VLA Humanoid-VLA:迈向整合第一人称视角的通用人形控制
  • 基于STM32、HAL库的AD7616BSTZ模数转换器ADC驱动程序设计
  • Linux操作系统学习---进程地址空间
  • 【LaTex】8.1 文档类与层级
  • 前端权限管理
  • 小刚说C语言刷题——1320时钟旋转
  • 生成式人工智能认证(GAI认证)要学哪些知识?
  • google chrome 中 fcitx5 候选框不跟随光标
  • 【SpringCloudAlibaba】Dubbo 和 Spring Cloud OpenFeign 在服务治理能力上的差异
  • 生成式人工智能认证(GAI认证)考试难吗?
  • SpringBoot的自动扫描特性-笔记
  • Vue初步总结-摘自 黑马程序员
  • 浅谈 MySQL 日志的原理
  • 新增 29 个专业,科技成为关键赛道!
  • 互联网的下一代脉搏:深入理解 QUIC 协议
  • Day23-Web开发——Linux