境外网站服务器,汕头企业网络推广,昆明网站建设开发,网站建设对公司有什么意义文章目录 C类与对象前言读者须知RVO 与 NRVO 的启用条件如何确认优化是否启用#xff1f; 1. 按值传递与拷贝省略1.1 按值传递的概念1.2 示例代码1.3 按值传递的性能影响1.3.1 完全不优化 1.4 不同编译器下的优化表现1.4.1 Visual Studio 2019普通优化1.4.2 Visual Studio 202… 文章目录 C类与对象前言读者须知RVO 与 NRVO 的启用条件如何确认优化是否启用 1. 按值传递与拷贝省略1.1 按值传递的概念1.2 示例代码1.3 按值传递的性能影响1.3.1 完全不优化 1.4 不同编译器下的优化表现1.4.1 Visual Studio 2019普通优化1.4.2 Visual Studio 2022激进优化 1.5 小结 2. 返回值优化RVO2.1 RVO 的概念2.2 示例代码2.3 不同优化下的表现2.3.1 完全不优化的情况2.3.2 启用 RVO 的情况Visual Studio 20192.3.3 激进 RVO 的情况Visual Studio 2022 2.4 小结 3. 命名返回值优化NRVO3.1 NRVO 的概念3.2 示例代码3.3 优化下的不同表现3.3.1 完全不优化的情况3.3.2 启用 NRVO 的情况Visual Studio 2019 和 2022 3.4 Visual Studio 2022 的优化对比3.5 小结 4. 赋值操作无法优化的原因4.1 赋值操作的本质4.2 示例代码 5. Visual Studio 2019 vs Visual Studio 2022 编译器优化差异5.1 编译器的工作原理5.2 为什么 VS2022 更加激进5.3 编译器的激进优化总结 6. 总结 C类与对象
前言 欢迎讨论如果你在学习过程中有任何问题或想法欢迎在评论区留言我们一起交流学习。你的支持是我继续创作的动力 点赞、收藏与分享觉得这篇文章对你有帮助吗别忘了点赞、收藏并分享给更多的小伙伴哦你们的支持是我不断进步的动力 分享给更多人如果你觉得这篇文章对你有帮助欢迎分享给更多对C感兴趣的朋友让我们一起进步 C 作为一门底层高效语言在设计时便考虑到了性能和资源管理。程序员在编写代码时常常面临对象的频繁创建与销毁尤其是在函数返回值的传递过程中可能会触发多次对象的拷贝构造或移动操作。为了减少这些不必要的拷贝C 编译器会采用一些优化技术如 拷贝省略Copy Elision、返回值优化Return Value OptimizationRVO和 命名返回值优化Named Return Value OptimizationNRVO。
读者须知
RVO 与 NRVO 的启用条件
虽然 RVO 和 NRVO 是编译器自动完成的优化但是这些优化并不总是启用具体取决于编译器的实现和配置。例如
在 C17 之前RVO 是一个可选优化但在 C17 标准之后RVO 被强制启用编译器必须在符合条件的情况下执行拷贝省略。NRVO 通常依赖于编译器的智能分析虽然大多数现代编译器都能支持 NRVO但其效果和激进程度因编译器和版本的不同而有所差异。
因此尽管 RVO 是 C 标准的一部分但 NRVO 则并不总是强制执行尤其是在复杂场景下不同的编译器版本可能表现出不同的优化行为。
如何确认优化是否启用
你可以通过编译时的优化级别和编译器选项来控制 RVO 和 NRVO 的启用。通常使用 -O2 或 -O3 优化级别可以启用这些优化。如果你希望查看编译器具体是否执行了这些优化可以通过以下方式进行检查
GCC使用 -fno-elide-constructors 禁用拷贝省略。Clang通过 -fno-elide-constructors 禁用拷贝省略。MSVCVisual Studio 中可以通过 /Od禁用优化或 /O2启用优化控制优化行为。
在本篇中主要使用VS2019和VS2022来进行比较因为实际情况的复杂性以及编译器版本的不同甚至同一大版本中小版本的不同更新的VS都存在一定的差异本篇输出结果示例仅作参考更多的是让读者通过不同优化的比较来理解现代编译器在提升程序效率所做的改进 1. 按值传递与拷贝省略
1.1 按值传递的概念 在 C 中按值传递意味着函数参数是通过创建实参对象的副本来传递的。通常会触发拷贝构造或移动构造函数。按值传递可以在函数内部修改参数副本而不影响原始实参对象但这也带来了额外的性能开销。 当我们传递一个对象给函数时编译器会为这个对象创建一个副本。这个副本的创建需要调用 拷贝构造函数并且在函数执行结束后该副本会被销毁从而调用 析构函数。这一过程涉及到内存的分配与释放对于大对象而言可能会导致性能下降。
1.2 示例代码
#include iostream
using namespace std;class A {
public:A(int a 0) : _a1(a) {cout A(int a) 构造函数被调用, _a _a1 endl;}A(const A aa) : _a1(aa._a1) {cout A(const A aa) 拷贝构造函数被调用 endl;}A(A aa) noexcept : _a1(aa._a1) {cout A(A aa) 移动构造函数被调用 endl;}~A() {cout ~A() 析构函数被调用 endl;}private:int _a1;
};void f1(A aa) {} // 按值传递int main() {A aa1(10); // 创建对象 aa1f1(aa1); // 按值传递调用拷贝构造return 0;
}1.3 按值传递的性能影响
在上述代码中按值传递会创建对象的副本并调用 拷贝构造函数 或 移动构造函数然后在函数执行结束时析构函数将会被调用。这一过程虽然实现了副本的安全传递但对于大型对象频繁的拷贝和析构会导致性能问题。
1.3.1 完全不优化
在没有任何优化的情况下按值传递时会创建一个对象的副本并调用拷贝构造函数。返回对象后析构函数将被调用两次一次是为原对象另一次是为副本。
输出结果
A(int a) 构造函数被调用, _a 10
A(const A aa) 拷贝构造函数被调用
~A() 析构函数被调用
~A() 析构函数被调用解释
对象 aa1 在主函数中通过构造函数被创建。按值传递时编译器调用了拷贝构造函数为 aa1 创建了副本。当函数 f1 执行结束后副本被销毁调用了析构函数。当 main 函数结束时原始对象 aa1 也被销毁。 #mermaid-svg-jesg0GMnvrOA3xfk {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-jesg0GMnvrOA3xfk .error-icon{fill:#552222;}#mermaid-svg-jesg0GMnvrOA3xfk .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-jesg0GMnvrOA3xfk .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-jesg0GMnvrOA3xfk .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-jesg0GMnvrOA3xfk .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-jesg0GMnvrOA3xfk .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-jesg0GMnvrOA3xfk .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-jesg0GMnvrOA3xfk .marker{fill:#333333;stroke:#333333;}#mermaid-svg-jesg0GMnvrOA3xfk .marker.cross{stroke:#333333;}#mermaid-svg-jesg0GMnvrOA3xfk svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-jesg0GMnvrOA3xfk .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-jesg0GMnvrOA3xfk .cluster-label text{fill:#333;}#mermaid-svg-jesg0GMnvrOA3xfk .cluster-label span{color:#333;}#mermaid-svg-jesg0GMnvrOA3xfk .label text,#mermaid-svg-jesg0GMnvrOA3xfk span{fill:#333;color:#333;}#mermaid-svg-jesg0GMnvrOA3xfk .node rect,#mermaid-svg-jesg0GMnvrOA3xfk .node circle,#mermaid-svg-jesg0GMnvrOA3xfk .node ellipse,#mermaid-svg-jesg0GMnvrOA3xfk .node polygon,#mermaid-svg-jesg0GMnvrOA3xfk .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-jesg0GMnvrOA3xfk .node .label{text-align:center;}#mermaid-svg-jesg0GMnvrOA3xfk .node.clickable{cursor:pointer;}#mermaid-svg-jesg0GMnvrOA3xfk .arrowheadPath{fill:#333333;}#mermaid-svg-jesg0GMnvrOA3xfk .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-jesg0GMnvrOA3xfk .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-jesg0GMnvrOA3xfk .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-jesg0GMnvrOA3xfk .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-jesg0GMnvrOA3xfk .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-jesg0GMnvrOA3xfk .cluster text{fill:#333;}#mermaid-svg-jesg0GMnvrOA3xfk .cluster span{color:#333;}#mermaid-svg-jesg0GMnvrOA3xfk div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-jesg0GMnvrOA3xfk :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 构造对象 aa1 拷贝构造副本传递给 f1 副本执行函数操作 析构副本 主函数结束时析构 aa1 1.4 不同编译器下的优化表现
1.4.1 Visual Studio 2019普通优化
在 Visual Studio 2019 中编译器在普通优化模式下依然会调用拷贝构造函数。
输出结果
A(int a) 构造函数被调用, _a 10
A(const A aa) 拷贝构造函数被调用
~A() 析构函数被调用
~A() 析构函数被调用尽管编译器启用了部分优化但在这种按值传递的情况下仍然需要调用拷贝构造函数并最终调用两次析构函数。
1.4.2 Visual Studio 2022激进优化
VS2022 的优化更加激进它能够跳过对象的拷贝构造直接传递原始对象的引用。通过内存重用和别名优化编译器可以避免创建副本。
输出结果
A(int a) 构造函数被调用, _a 10
~A() 析构函数被调用解释
在 VS2022 中拷贝构造函数被优化掉编译器直接将原对象 aa1 传递给函数 f1。说白了就是编译器上下文分析发现函数里面aa不会修改那直接就在函数里面使用aa1即可此时函数里面的aa就是aa1的别名无需创建副本也不需要析构副本只在 main 函数结束时销毁 aa1。 #mermaid-svg-kdiUczaKD1ZVCXsq {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-kdiUczaKD1ZVCXsq .error-icon{fill:#552222;}#mermaid-svg-kdiUczaKD1ZVCXsq .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-kdiUczaKD1ZVCXsq .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-kdiUczaKD1ZVCXsq .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-kdiUczaKD1ZVCXsq .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-kdiUczaKD1ZVCXsq .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-kdiUczaKD1ZVCXsq .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-kdiUczaKD1ZVCXsq .marker{fill:#333333;stroke:#333333;}#mermaid-svg-kdiUczaKD1ZVCXsq .marker.cross{stroke:#333333;}#mermaid-svg-kdiUczaKD1ZVCXsq svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-kdiUczaKD1ZVCXsq .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-kdiUczaKD1ZVCXsq .cluster-label text{fill:#333;}#mermaid-svg-kdiUczaKD1ZVCXsq .cluster-label span{color:#333;}#mermaid-svg-kdiUczaKD1ZVCXsq .label text,#mermaid-svg-kdiUczaKD1ZVCXsq span{fill:#333;color:#333;}#mermaid-svg-kdiUczaKD1ZVCXsq .node rect,#mermaid-svg-kdiUczaKD1ZVCXsq .node circle,#mermaid-svg-kdiUczaKD1ZVCXsq .node ellipse,#mermaid-svg-kdiUczaKD1ZVCXsq .node polygon,#mermaid-svg-kdiUczaKD1ZVCXsq .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-kdiUczaKD1ZVCXsq .node .label{text-align:center;}#mermaid-svg-kdiUczaKD1ZVCXsq .node.clickable{cursor:pointer;}#mermaid-svg-kdiUczaKD1ZVCXsq .arrowheadPath{fill:#333333;}#mermaid-svg-kdiUczaKD1ZVCXsq .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-kdiUczaKD1ZVCXsq .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-kdiUczaKD1ZVCXsq .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-kdiUczaKD1ZVCXsq .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-kdiUczaKD1ZVCXsq .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-kdiUczaKD1ZVCXsq .cluster text{fill:#333;}#mermaid-svg-kdiUczaKD1ZVCXsq .cluster span{color:#333;}#mermaid-svg-kdiUczaKD1ZVCXsq div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-kdiUczaKD1ZVCXsq :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 构造对象 aa1 直接传递 aa1 到 f1 无拷贝构造, 直接操作 主函数结束时析构 aa1 1.5 小结
按值传递通常会触发拷贝构造或移动构造并在函数结束时触发析构函数。Visual Studio 2019 中普通优化仍然会调用拷贝构造函数。Visual Studio 2022 的激进优化则可以跳过拷贝构造避免副本的创建。 2. 返回值优化RVO
2.1 RVO 的概念 返回值优化RVO 是编译器的一种优化技术它允许编译器在函数返回临时对象时 直接在调用者的内存空间中构造该对象避免不必要的拷贝或移动构造。 当函数返回一个局部临时对象时通常会触发一次拷贝构造或移动构造因为局部对象需要从函数内部复制到外部。然而RVO 能够避免这种多余的拷贝或移动操作编译器直接在调用者的内存空间中构造返回的对象-。
2.2 示例代码
A f2() {A aa(5);return aa; // 返回局部临时对象
}int main() {A a2 f2(); // 接收返回值return 0;
}2.3 不同优化下的表现
2.3.1 完全不优化的情况
在没有启用 RVO 的情况下返回值会经历多次拷贝操作
在 f2() 内部创建局部对象 aa。创建一个临时对象将 aa 拷贝到这个临时对象中。最后将临时对象拷贝给 a2并调用两次拷贝构造函数。
输出结果
A(int a) 构造函数被调用, _a 5
A(const A aa) 拷贝构造函数被调用
A(const A aa) 拷贝构造函数被调用
~A() 析构函数被调用
~A() 析构函数被调用
~A() 析构函数被调用解释
局部对象 aa 在 f2 函数内创建并通过两次拷贝构造传递给 a2。三次析构函数分别销毁局部对象 aa、临时对象和最终返回的 a2。 #mermaid-svg-B319EdfjB82hYa5v {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-B319EdfjB82hYa5v .error-icon{fill:#552222;}#mermaid-svg-B319EdfjB82hYa5v .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-B319EdfjB82hYa5v .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-B319EdfjB82hYa5v .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-B319EdfjB82hYa5v .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-B319EdfjB82hYa5v .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-B319EdfjB82hYa5v .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-B319EdfjB82hYa5v .marker{fill:#333333;stroke:#333333;}#mermaid-svg-B319EdfjB82hYa5v .marker.cross{stroke:#333333;}#mermaid-svg-B319EdfjB82hYa5v svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-B319EdfjB82hYa5v .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-B319EdfjB82hYa5v .cluster-label text{fill:#333;}#mermaid-svg-B319EdfjB82hYa5v .cluster-label span{color:#333;}#mermaid-svg-B319EdfjB82hYa5v .label text,#mermaid-svg-B319EdfjB82hYa5v span{fill:#333;color:#333;}#mermaid-svg-B319EdfjB82hYa5v .node rect,#mermaid-svg-B319EdfjB82hYa5v .node circle,#mermaid-svg-B319EdfjB82hYa5v .node ellipse,#mermaid-svg-B319EdfjB82hYa5v .node polygon,#mermaid-svg-B319EdfjB82hYa5v .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-B319EdfjB82hYa5v .node .label{text-align:center;}#mermaid-svg-B319EdfjB82hYa5v .node.clickable{cursor:pointer;}#mermaid-svg-B319EdfjB82hYa5v .arrowheadPath{fill:#333333;}#mermaid-svg-B319EdfjB82hYa5v .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-B319EdfjB82hYa5v .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-B319EdfjB82hYa5v .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-B319EdfjB82hYa5v .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-B319EdfjB82hYa5v .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-B319EdfjB82hYa5v .cluster text{fill:#333;}#mermaid-svg-B319EdfjB82hYa5v .cluster span{color:#333;}#mermaid-svg-B319EdfjB82hYa5v div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-B319EdfjB82hYa5v :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 构造局部对象 aa 拷贝到临时对象 将临时对象拷贝给 a2 销毁局部对象 aa 销毁临时对象 销毁 a2 2.3.2 启用 RVO 的情况Visual Studio 2019
在 Visual Studio 2019 中编译器启用了 RVO 优化避免了创建临时对象直接将aa拷贝给a2.
输出结果
A(int a) 构造函数被调用, _a 5
A(const A aa) 拷贝构造函数被调用
~A() 析构函数被调用
~A() 析构函数被调用解释
编译器避免了临时对象的创建但仍通过拷贝构造将 aa 传递给 a2。整个过程调用了一次拷贝构造并在 a2 和 aa 被销毁时分别调用析构函数。 #mermaid-svg-wTmX4seC74fC21GS {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-wTmX4seC74fC21GS .error-icon{fill:#552222;}#mermaid-svg-wTmX4seC74fC21GS .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-wTmX4seC74fC21GS .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-wTmX4seC74fC21GS .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-wTmX4seC74fC21GS .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-wTmX4seC74fC21GS .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-wTmX4seC74fC21GS .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-wTmX4seC74fC21GS .marker{fill:#333333;stroke:#333333;}#mermaid-svg-wTmX4seC74fC21GS .marker.cross{stroke:#333333;}#mermaid-svg-wTmX4seC74fC21GS svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-wTmX4seC74fC21GS .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-wTmX4seC74fC21GS .cluster-label text{fill:#333;}#mermaid-svg-wTmX4seC74fC21GS .cluster-label span{color:#333;}#mermaid-svg-wTmX4seC74fC21GS .label text,#mermaid-svg-wTmX4seC74fC21GS span{fill:#333;color:#333;}#mermaid-svg-wTmX4seC74fC21GS .node rect,#mermaid-svg-wTmX4seC74fC21GS .node circle,#mermaid-svg-wTmX4seC74fC21GS .node ellipse,#mermaid-svg-wTmX4seC74fC21GS .node polygon,#mermaid-svg-wTmX4seC74fC21GS .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-wTmX4seC74fC21GS .node .label{text-align:center;}#mermaid-svg-wTmX4seC74fC21GS .node.clickable{cursor:pointer;}#mermaid-svg-wTmX4seC74fC21GS .arrowheadPath{fill:#333333;}#mermaid-svg-wTmX4seC74fC21GS .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-wTmX4seC74fC21GS .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-wTmX4seC74fC21GS .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-wTmX4seC74fC21GS .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-wTmX4seC74fC21GS .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-wTmX4seC74fC21GS .cluster text{fill:#333;}#mermaid-svg-wTmX4seC74fC21GS .cluster span{color:#333;}#mermaid-svg-wTmX4seC74fC21GS div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-wTmX4seC74fC21GS :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 构造局部对象 aa 将 aa 拷贝到 a2 销毁局部对象 aa 销毁 a2 2.3.3 激进 RVO 的情况Visual Studio 2022
Visual Studio 2022 实现了更加激进的 RVO 优化。编译器直接在 a2 的内存空间中构造对象 aa完全跳过拷贝构造。其实就是下文讲的NRVO
输出结果
A(int a) 构造函数被调用, _a 5
~A() 析构函数被调用解释
aa 直接在 a2 的内存空间中构造避免了临时对象和拷贝构造。最终只需要调用一次析构函数来销毁 a2。 #mermaid-svg-DPY5uAej1uu3iqNO {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-DPY5uAej1uu3iqNO .error-icon{fill:#552222;}#mermaid-svg-DPY5uAej1uu3iqNO .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-DPY5uAej1uu3iqNO .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-DPY5uAej1uu3iqNO .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-DPY5uAej1uu3iqNO .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-DPY5uAej1uu3iqNO .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-DPY5uAej1uu3iqNO .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-DPY5uAej1uu3iqNO .marker{fill:#333333;stroke:#333333;}#mermaid-svg-DPY5uAej1uu3iqNO .marker.cross{stroke:#333333;}#mermaid-svg-DPY5uAej1uu3iqNO svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-DPY5uAej1uu3iqNO .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-DPY5uAej1uu3iqNO .cluster-label text{fill:#333;}#mermaid-svg-DPY5uAej1uu3iqNO .cluster-label span{color:#333;}#mermaid-svg-DPY5uAej1uu3iqNO .label text,#mermaid-svg-DPY5uAej1uu3iqNO span{fill:#333;color:#333;}#mermaid-svg-DPY5uAej1uu3iqNO .node rect,#mermaid-svg-DPY5uAej1uu3iqNO .node circle,#mermaid-svg-DPY5uAej1uu3iqNO .node ellipse,#mermaid-svg-DPY5uAej1uu3iqNO .node polygon,#mermaid-svg-DPY5uAej1uu3iqNO .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-DPY5uAej1uu3iqNO .node .label{text-align:center;}#mermaid-svg-DPY5uAej1uu3iqNO .node.clickable{cursor:pointer;}#mermaid-svg-DPY5uAej1uu3iqNO .arrowheadPath{fill:#333333;}#mermaid-svg-DPY5uAej1uu3iqNO .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-DPY5uAej1uu3iqNO .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-DPY5uAej1uu3iqNO .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-DPY5uAej1uu3iqNO .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-DPY5uAej1uu3iqNO .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-DPY5uAej1uu3iqNO .cluster text{fill:#333;}#mermaid-svg-DPY5uAej1uu3iqNO .cluster span{color:#333;}#mermaid-svg-DPY5uAej1uu3iqNO div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-DPY5uAej1uu3iqNO :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 直接在 a2 内存空间中构造 aa 销毁 a2 2.4 小结
RVO 主要用于返回临时对象的优化能够在返回局部对象时避免多次拷贝。Visual Studio 2019 中启用了 RVO减少了临时对象的创建但仍会调用一次拷贝构造。Visual Studio 2022 则更加激进完全避免了拷贝构造直接在返回对象的目标内存空间中构造该对象。 3. 命名返回值优化NRVO
3.1 NRVO 的概念 命名返回值优化NRVO 是 RVO 的扩展专门用于优化函数返回命名局部变量的情况。编译器会在调用者的内存空间中直接构造该命名对象避免临时对象和拷贝操作。 NRVO 允许编译器在返回函数内的命名局部变量时进行优化直接在目标对象的内存中构造该局部变量而不是创建一个临时对象进行拷贝或移动。这一优化虽然不像 RVO 那样是 C 标准的强制要求但大多数现代编译器都会尝试实现这种优化。
3.2 示例代码
A f3() {A a(3);return a; // 返回命名局部变量
}int main() {A a2 f3(); // 使用返回值return 0;
}在这段代码中函数 f3 返回命名局部变量 a。没有 NRVO 优化的情况下a 会首先被拷贝到一个临时对象中然后该临时对象会被拷贝到 a2。
3.3 优化下的不同表现
3.3.1 完全不优化的情况
在没有 NRVO 优化的情况下返回的命名对象 a 会经历以下拷贝过程
在 f3 函数内创建局部对象 a。创建一个临时对象将 a 拷贝到这个临时对象中。最后将临时对象拷贝到 a2 中。
输出结果
A(int a) 构造函数被调用, _a 3
A(const A aa) 拷贝构造函数被调用
A(const A aa) 拷贝构造函数被调用
~A() 析构函数被调用
~A() 析构函数被调用
~A() 析构函数被调用解释
局部变量 a 在 f3 中创建并通过两次拷贝构造传递给 a2。由于没有启用 NRVO因此返回值会触发两次拷贝构造和三次析构函数调用。
3.3.2 启用 NRVO 的情况Visual Studio 2019 和 2022
在 Visual Studio 2019 和 Visual Studio 2022 中NRVO 技术的实现基本一致。局部对象 a 会直接在 a2 的内存空间中构造没有临时对象和多余的拷贝操作。
输出结果
A(int a) 构造函数被调用, _a 3
~A() 析构函数被调用解释
通过 NRVO编译器直接在 a2 的内存空间中构造局部对象 a避免了拷贝构造。整个过程只需要一次析构调用销毁 a2。
3.4 Visual Studio 2022 的优化对比 复杂场景中的 NRVO Visual Studio 2022 在处理复杂的函数返回场景时表现更为激进。例如在多层嵌套、条件判断等情况下NRVO 依然有效而某些编译器可能在复杂条件下无法实现优化。 以下是一个复杂的 NRVO 示例
A f4(bool flag) {A a1(1);A a2(2);if (flag) {return a1;} else {return a2;}
}int main() {A a3 f4(true); // 使用返回值return 0;
}在这种复杂场景中Visual Studio 2022 依然能够直接在 a3 的内存空间中构造返回值无论是 a1 还是 a2而不会创建临时对象或额外的拷贝构造。并且这种情况下发现只需要返回a1那甚至可能会跳过a2的创建
输出结果
A(int a) 构造函数被调用, _a 1
~A() 析构函数被调用3.5 小结
NRVO 针对命名局部变量的优化能够在返回命名变量时避免临时对象和拷贝构造。Visual Studio 2019 和 2022 的 NRVO 实现基本一致能够在大多数情况下避免拷贝构造。Visual Studio 2022 在处理复杂场景时的 NRVO 优化表现更为激进即使在条件判断和嵌套场景中也能有效避免额外的临时对象和拷贝。 4. 赋值操作无法优化的原因
4.1 赋值操作的本质 赋值操作与对象构造不同它修改已经存在的对象因此不能像RVO或NRVO那样进行优化。赋值操作必须真正执行对象状态的复制无法通过跳过拷贝来优化。 在 C 中赋值操作是将一个对象的内容复制到另一个对象中。这与对象的构造不同因为在赋值操作时目标对象已经存在不能通过构造优化来避免对象的状态复制。
4.2 示例代码
A aa1(10);
A aa2(20);
aa1 aa2; // 赋值操作输出结果
A(int a) 构造函数被调用, _a 10
A(int a) 构造函数被调用, _a 20
A operator(const A aa) 赋值运算符被调用
~A() 析构函数被调用
~A() 析构函数被调用解释
对象 aa1 和 aa2 分别通过构造函数创建。赋值操作需要实际复制 aa2 的数据到 aa1 中因此必须调用赋值运算符。 #mermaid-svg-HxKVVdxCZckLjEw7 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-HxKVVdxCZckLjEw7 .error-icon{fill:#552222;}#mermaid-svg-HxKVVdxCZckLjEw7 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-HxKVVdxCZckLjEw7 .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-HxKVVdxCZckLjEw7 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-HxKVVdxCZckLjEw7 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-HxKVVdxCZckLjEw7 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-HxKVVdxCZckLjEw7 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-HxKVVdxCZckLjEw7 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-HxKVVdxCZckLjEw7 .marker.cross{stroke:#333333;}#mermaid-svg-HxKVVdxCZckLjEw7 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-HxKVVdxCZckLjEw7 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-HxKVVdxCZckLjEw7 .cluster-label text{fill:#333;}#mermaid-svg-HxKVVdxCZckLjEw7 .cluster-label span{color:#333;}#mermaid-svg-HxKVVdxCZckLjEw7 .label text,#mermaid-svg-HxKVVdxCZckLjEw7 span{fill:#333;color:#333;}#mermaid-svg-HxKVVdxCZckLjEw7 .node rect,#mermaid-svg-HxKVVdxCZckLjEw7 .node circle,#mermaid-svg-HxKVVdxCZckLjEw7 .node ellipse,#mermaid-svg-HxKVVdxCZckLjEw7 .node polygon,#mermaid-svg-HxKVVdxCZckLjEw7 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-HxKVVdxCZckLjEw7 .node .label{text-align:center;}#mermaid-svg-HxKVVdxCZckLjEw7 .node.clickable{cursor:pointer;}#mermaid-svg-HxKVVdxCZckLjEw7 .arrowheadPath{fill:#333333;}#mermaid-svg-HxKVVdxCZckLjEw7 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-HxKVVdxCZckLjEw7 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-HxKVVdxCZckLjEw7 .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-HxKVVdxCZckLjEw7 .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-HxKVVdxCZckLjEw7 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-HxKVVdxCZckLjEw7 .cluster text{fill:#333;}#mermaid-svg-HxKVVdxCZckLjEw7 .cluster span{color:#333;}#mermaid-svg-HxKVVdxCZckLjEw7 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-HxKVVdxCZckLjEw7 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 构造对象 aa1 和 aa2 调用赋值操作 将 aa2 的内容复制到 aa1 析构 aa2 和 aa1 赋值操作在 C 中并没有优化空间因为在赋值操作时目标对象已经存在编译器必须实际执行数据复制的过程而不能通过拷贝省略来进行优化。 5. Visual Studio 2019 vs Visual Studio 2022 编译器优化差异
5.1 编译器的工作原理
编译器在优化过程中使用了别名分析和内存重用技术。在分析对象的使用模式后编译器能够判断某些对象的拷贝是多余的可以直接复用原始对象的内存地址。这种优化策略依赖于编译器对代码中对象生命周期的深层次分析。
5.2 为什么 VS2022 更加激进
VS2022 能够在更多复杂场景下进行优化包括跨行优化、多层函数调用等。这是因为编译器能够更好地理解对象的生命周期并通过对象生命周期分析来跳过冗余的拷贝操作。
例如在以下代码中
A f4() {A a1(1);A a2(2);return a1; // 返回局部变量
}int main() {A a3 f4();return 0;
}VS2019 的输出结果
A(int a) 构造函数被调用, _a 1
A(int a) 构造函数被调用, _a 2
A(const A aa) 拷贝构造函数被调用
~A() 析构函数被调用
~A() 析构函数被调用
~A() 析构函数被调用在 VS2019 中即使返回的是局部变量仍会创建一个临时对象然后通过拷贝构造将其传递给 a3。
VS2022 的输出结果
A(int a) 构造函数被调用, _a 1
~A() 析构函数被调用在 VS2022 中编译器能够更好地分析对象生命周期跳过了临时对象的创建直接在 a3 的内存空间中构造返回的局部变量 a1。
5.3 编译器的激进优化总结
Visual Studio 2019 在大部分情况下能够启用 RVO 和 NRVO但在某些复杂场景下仍需要额外的拷贝构造。Visual Studio 2022 的优化更加激进通过更好的对象生命周期分析能够避免更多不必要的拷贝操作即使在复杂的函数调用和条件判断中仍能高效地进行返回值优化。 6. 总结
通过本文我们深入分析了 C 中编译器优化的几个重要方面包括 返回值优化RVO 和 命名返回值优化NRVO。这些优化能够显著减少对象的拷贝构造和临时对象的创建从而提升程序的执行效率。
RVO 主要用于优化返回临时对象的场景Visual Studio 2022 通过激进优化完全跳过了拷贝构造。NRVO 则用于优化返回命名局部变量的场景Visual Studio 2019 和 2022 的 NRVO 实现基本一致但 2022 的编译器在复杂场景中的表现更为出色。在涉及对象赋值的场景中由于目标对象已经存在因此无法通过 RVO 或 NRVO 进行优化。
现代编译器已经能够通过 别名分析 和 对象生命周期分析 实现高度智能的优化。程序员不需要显式地进行优化只需合理设计函数返回结构编译器会自动帮助完成优化。
如果你希望了解更多编译器优化的底层机制可以查阅 cppreference RVO文档 和 MSVC优化指南。 以上就是关于C类与对象深度解析六全面剖析拷贝省略、RVO、NRVO优化策略的内容啦各位大佬有什么问题欢迎在评论区指正或者私信我也是可以的啦您的支持是我创作的最大动力❤️