%% # 纲要 > 主干纲要、Hint/线索/路标 # Q&A #### 已明确 #### 待明确 > 当下仍存有的疑惑 **❓<font color="#c0504d"> 有什么问题?</font>** %% # 拷贝省略 Copy Elision [拷贝省略(Copy Elision)](https://en.cppreference.com/w/cpp/language/copy_elision)是 C++中的一个优化技术,允许编译器在某些情况下**省略临时对象的拷贝和移动操作**,旨在避免不必要的资源分配与释放,提升程序执行效率。 - 在 C++17 之前,拷贝省略属于 "编译器" 自身的优化行为,例如 RVO/NRVO 优化。 - 自 C++17 起,对拷贝省略的规则进行了**扩展和明确化**,将某些情况的拷贝省略**纳入了标准规定**(即从 "**编译器可选**" 变为 "**强制/编译器必须**"),**从 "语义标准" 的层面上保证了拷贝省略的行为**,而不再是仅依赖于编译器自身的优化策略。 > [!caution] > - 在 **`return` 语句**或 **` throw ` 表达式**中,如果满足或将满足拷贝省略的触发条件,但编译器因为某些原因不能执行拷贝省略,则**编译器将尝试使用==移动构造函数==** (除非源对象是函数参数) > > > > - 在**常量表达式**(constant expression)或**常量初始化**(constant initialization)中,不会执行拷贝省略。 <br> ## 强制省略(mandatory copy elision) 自 C++17 起,**纯右值 (`prvalue`) 直到需要使用时才会被具现化(materialized)**,并直接**构造到其最终所处的上下文的内存空间中**。 因此,在以下几个涉及 "**==纯右值语义==**"(prvalue semantics) 的场景中,C++17 标准要求编译器**必须/强制执行拷贝省略**: - **函数返回一个==临时对象==** 时(仅 **URVO** 被标准规定为必须执行,而不包含 NRVO,后者仍是可选的) - `return MyClass();` - **==临时对象==用于初始化时**:用临时对象(`prvalue`)初始化 "**同类型的**另一个对象"时,省略拷贝/移动构造函数。 - `MyClass obj = MyClass();` - **==临时对象==用作函数形参时**:将临时对象(`prvalue`)作为函数参数传递时,省略拷贝/移动构造函数。 - `SomeFunc(MyClass());` <br> ## 非强制性拷贝省略(non-mandatory copy/move elision) 在其他场景下,**"拷贝省略" 仍然是编译器可选的**,C++标准不强制要求。 包括: - **NRVO 优化** - 在 `throw` 表达式中,当操作数是 **`non-volatile` 的自动变量**,且其并非函数参数、catch 子句参数、且其作用域没有扩展到最内层的 `try` 语句块时(如果存在 `try` 语句块); - 在 catch 子句中,当**参数与抛出的异常对象的类型相同**(忽略 cv-qualification) 时,**对==异常对象的拷贝==将被省略**。 - catch 子句体中,将直接访问异常对象,就像通过引用捕获一样。 - 在协程中... since C++20 <br><br><br> # RVO 返回值优化 **==返回值优化==** (Return Value Optimization,RVO),是编译器提供的一种**针对 "函数返回值"** 的编译层面的优化[^1]。 返回值优化发生在 "函数需要**返回一个==函数体内==构建的、==与函数返回类型相同==的==局部对象==**" 的场景。 编译器执行返回值优化时,**将直接在 "==调用者期望的目标位置==" 构造 "将返回的对象"**,从而**省略中间的任何移动、拷贝构造**。 > [!caution] 函数形参与此无关!当返回函数形参时,其不能被进行优化 编译器提供的 **返回值优化** (Return Value Optimization,RVO)包括两种: - **==URVO== 优化**(Unnamed Return Value Optimization):未命名的返回值优化 - **==NRVO== 优化**(Named Return Value Optimization):具名的返回值优化 > [!info] URVO 与 NRVO 的区别 > URVO 优化与 NRVO 优化的区别只在于返回值 "**是否为 ==具名(具有标识符)的局部左值对象==** ": > - 如果 `return` 语句**直接返回一个临时对象 (`prvalue`)**,则属于 **==URVO== 优化**; > - 如果 `return` 语句返回一个**具名的局部变量**,则属于 **==NRVO== 优化**。 > > [!NOTE] **RVO 优化**是**编译器本身提供的功能**,在 C++11 标准之前就已经被大多数编译器支持,**其与移动语义无关**,**不依赖于移动构造函数**。 > [!caution] 在满足触发 `RVO` 优化的场景下,应用 `std::move()` 是多余的! > > C++标准关于 RVO 的部分表明,**如果满足 RVO 的条件**,由于某种原因 **编译器未能应用 RVO 优化**,则返回的对象 **==必须被视为右值==**。 > 实际上,**标准要求当 RVO 被允许时**,**要么实行拷贝消除,要么==将 `std::move` 隐式应用==于返回的局部对象**。 > > ```cpp > MyClass createObject() { > MyClass obj; > //... > return obj; // 如果编译器未执行RVO优化, 则会等价于隐式地应用下列语句. 因此, 显式使用`std::move()` 是多余的. > // return std::move(obj); > } > ``` > > > [!info] 可以将 RVO 优化关闭,可以对 g++增加选项 `-fno-elide-constructors` > <br> ### 返回值优化的原理 这一优化是编译器**在编译阶段**实现的,而非运行时的动态决策。 编译器在进行代码分析后,若确定可进行返回值优化,则**在生成代码时将==更改返回对象的存储位置==**——**直接在 "==调用函数的上下文==" 中分配该对象的存储空间**,而不是在函数的局部作用域内。由此,函数体内构造的、**将返回的局部对象实际位于==函数调用者的栈帧上==**。具体过程为: - **在==调用者的栈帧==上分配空间**:编译器会在函数调用者的栈帧上**预留空间用于构造返回对象**; - **调整返回指令**:编译器调整函数的返回指令,以确保不会移动或拷贝对象,而是**直接使用预留空间的地址**。 经过这一底层优化,编译器确保当函数返回时,**不需要再通过调用拷贝/移动构造函数来创建一个新的对象副本**。 <br> ### 返回值优化触发场景 URVO 触发场景:直接返回**临时对象** ```cpp title:rvo_exam.cpp MyClass createObject() { //... return MyClass(); // 可以应用URVO, 编译器直接在返回值的位置构造obj, 省略拷贝/移动构造. } ``` NRVO 触发场景:返回具名的**局部对象** ```cpp title:nrvo_exam.cpp MyClass createObject() { MyClass obj; // 具名的局部对象 //... return obj; // 可以应用NRVO, 编译器直接在返回值的位置构造obj, 省略拷贝/移动构造. } ``` URVO/NRVO **==不适用==** 的场景:返回**函数的参数** > [!caution] RVO/NRVO 不适用的返回场景 > > 函数直接返回 "**传入的形参对象**" 时,不适用 URVO/NRVO,将**会调用拷贝构造函数**。 > > ```cpp title: no_rvo. cpp > // 函数直接返回 "传入的形参", 不适用 URVO/NRVO,会调用拷贝构造函数。 > MyClass func1 (MyClass obj) { // 该函数将会触发两次"拷贝构造函数"的调用: 传参时, 函数返回时; > return obj; > } > > MyClass func2 (const MyClass& obj) { // 该函数将会触发一次"拷贝构造函数"的调用: 函数返回值时; > return obj; > } > ``` > > <br><br><br> # 常量传播(Const Propagation) **常量传播(Const Propagation)** 是由编译器提供的 "**编译优化技术**",其作用是: - **将程序中==已知的常量值==替换到表达式中。** - **消除不必要的变量,==减少内存访问==,提高程序执行效率。** 简单来说,**如果某个变量的值在编译时可以确定为一个常量**,编译器就会**在所有使用该变量的地方直接替换这个常量值**,从而减少不必要的变量访问和计算。 ![[02-开发笔记/01-cpp/类与对象/cpp-类的数据成员#^mku4i3]] > [!NOTE] > ![[_attachment/02-开发笔记/01-cpp/编译器相关/cpp-编译器优化.assets/IMG-cpp-编译器优化-C3D14BDF4DE15C038345B65CB2A79A8F.png|464]] <br><br><br> # Buffer ## 闪念 > sudden idea ## 候选资料 > Read it later # ♾️参考资料 # Footnotes [^1]: [C++ 函数返回对象时并没有调用拷贝构造函数](https://blog.csdn.net/nbu_dahe/article/details/119142610)