C++返回值的“拷贝”问题

C++拷贝和赋值、浅拷贝和深拷贝详解 1 前言 在C++Primer中(P440)中阐述了一个类中的拷贝控制操作有以下4类: (1)拷贝构造函数 (2)拷贝赋值运算符 (3)析构函数 (4)移动构造函数 (5)移动赋值运算符 其中,(4)和(5)是新的C++标准特性。 这里先讨论拷贝构造函数和赋值运算符的区别,什么时候会调用这两个构造函数。 然后讨论浅拷贝和深拷贝的区别。 1 拷贝构造函数和赋值运算符的... 阅读详情

这个问题本不是什么新鲜玩意了,但最近在和同事调试程序性能的时候,经常会提起这个问题。看来编译器的RVO和NRVO这个问题还是没有达到普及性。

先看一段代码:

#include <iostream>

using namespace std;


class MyClass
{
public:
    explicit MyClass() : m_value(0)
    {
        cout<<"MyClass Ctor!"<<endl;
    }

    MyClass(const MyClass &other) : m_value(other.m_value)
    {
        cout<<"MyClass Copy Ctor!"<<endl;
    }

    MyClass &operator =(const MyClass &other)
    {
        cout<<"MyClass Copy Operator!"<<endl;

        this->m_value = other.m_value;
        return *this;
    }

    ~MyClass()
    {
        cout<<"MyClass Dtor!"<<endl;
    }

    int m_value;
};

MyClass getMyObject()
{
    MyClass myObj;
    myObj.m_value = 2;
    return myObj;
}

void getMyObject(MyClass &myObj)
{
    myObj.m_value = 2;
}

int main()
{    
    //MyClass retObj = getMyObject(); // 调用1

    MyClass retObj;
    getMyObject(retObj); // 调用2

    return 0;
}

调用1和调用2的输出结果相同,如下截图:
这里写图片描述

我们可以清楚的看到,调用1的过程并不像我们想象的那样,会调用类的拷贝构造函数。这其实是编译器自动使用了NRVO(具名返回值优化技术),即在某些情况下编译器会避免无谓的拷贝操作。因此,不管是直接返回值还是通过引用回传值从理论上讲不会有任何空间效率和时间效率上的差异。
我们再简单的修改一下代码,在函数实现体里面增加一个分支:


MyClass getMyObject(bool even)
{
    MyClass myObj2, myObj5;
    if (even)
    {
        myObj2.m_value = 2;
        return myObj2;
    }
    else
    {
        myObj5.m_value = 5;
        return myObj5;
    }
}

然后调用:

int main()
{    
    MyClass retObj = getMyObject(true); // 调用1
    return 0;
}

输出结果如下:
这里写图片描述
我去!什么玩意?这是?NRVO呢?
不要着急,且慢,让我们再把代码作一点微小的调整:

MyClass getMyObject(bool even)
{
    MyClass myObj;
    if (even)
    {
        myObj.m_value = 2;
    }
    else
    {
        myObj.m_value = 5;
    }

    return myObj;
}

我们再次运行,得到输出结果如下:
这里写图片描述
看到上图,你是不是想骂: “NRVO你还有没有节操?一会儿这样一会儿那样?”。
关于这个问题的解释,推荐一篇文章 RVO V.S. std::move供阅读.
这篇文章用几幅图展示了函数执行时栈的增长方式。大概意思是,函数执行时,会有局部变量区,返回值区,函数调用区等。如果采用RVO的话,其实是在函数返回的时候直接覆写了返回值区,但如果函数中有多个return分支的话,编译器不知道该覆写哪个返回值区?
—— 这个有点绕,我也不太理解。不过不要紧,我们再把代码做一丁点修改。

MyClass getMyObject(bool even)
{
    if (even)
    {
        return MyClass();
    }

    MyClass myObj;
    myObj.m_value = 5;

    return myObj;
}

调用代码:


int main()
{    
    MyClass myobj = getMyObject(true);

    return 0;
}

运行输出结果如下:
这里写图片描述
这个时候输入的参数 even == true, 也就是说,执行的是if 分支,且return了一个匿名的临时对象, 编译器选择了 RVO。

我们再来一次代码修改:“把if分支的返回对象改为具名的局部对象”。

MyClass getMyObject(bool even)
{
    if (even)
    {
        MyClass myTempObj;
        return myTempObj;
    }

    MyClass myObj;
    myObj.m_value = 5;

    return myObj;
}

调用代码的输入参数保持不变,依然为 even == true

int main()
{    
    MyClass myobj = getMyObject(true);

    return 0;
}

运行输出结果如下:
这里写图片描述

以上测试的是函数执行if分支。现在我们把输入的参数改为假,即 even == false. 让函数执行正常流程。

int main()
{    
    MyClass myobj = getMyObject(false);

    return 0;
}

运行输出结果如下:
这里写图片描述

把if 分支的具名局部对象去掉呢?会是什么结果?

MyClass getMyObject(bool even)
{
    if (even)
    {
        return MyClass();
    }

    MyClass myObj;
    myObj.m_value = 5;

    return myObj;
}

调用代码的输入参数保持不变,依然为 even == false。
运行输出结果如下:
这里写图片描述
结果一样。

欲进一步探究细节,可以搜一下更多RVO或NRVO或者C++11右值引用、Move语义的相关文章读一读。

最后,我们再次通过下面这段代码来验证一下直接返回容器数据和通过引用返回容器数据的时间效率。

const int BIG_VALUE = 60000000; //  6千万
vector<float> getDatas()
{
    vector<float> datas;
    for (int i=0; i<BIG_VALUE; ++i)
    {
        datas.push_back(sqrt(i));
    }

    return datas;
}

void getDatas(vector<float> &datas)
{
    for (int i=0; i<BIG_VALUE; ++i)
    {
        datas.push_back(sqrt(i));
    }
}

int main()
{    
    double start = GetTickCount();
    vector<float> datas = getDatas();
    double  end = GetTickCount();
    cout << "getDatas()-time="<< (end - start) << endl;

    double refstart = GetTickCount();
    vector<float> refdatas;
    getDatas(refdatas);
    double  refend = GetTickCount();
    cout << "getDatas(ref)-time="<< (refend - refstart) << endl;

    return 0;
}

程序输出如下:
这里写图片描述
还能说什么呢?通过引用的时间效率居然还远远差于直接返回值的时间效率。
不知道是否与平台有关系,我测试的平台为windows,编译器是mingw4.4。

C++中的返回值优化(RVO) C++的返回值优化RVO 阅读详情

相关推荐

团子杂记:SAP收入确认工具RAR(Revenue Recognition&Reporting)在新收入准则下的应用

SAP收入确认工具RAR知识分享

Lucas1211的博客 4284

C++中函数返回值与拷贝

C++中函数返回值与拷贝 五一假期最后一天,看JUC看的头疼,写写blog放松一下。作为自己的第一篇blog,自己就先来谈谈对C++中函数返回return的理解,自己本来在学Java,但是平时学校的项目是用的C++,所以在平时搬砖时经常会有一些问题,今天就来谈谈前段时间注意到的一个很小的知识点,话不多说,先上列子。 首先我们创建一个简单的Man类,实现它的无参构造函数、有参构造函数和析构函数: c...

qq_39913402的博客 4635

自动化测试-自愈技术

随着人工智能的发展,作为一名软件测试工程师,相信大家都会思考如何把这一前沿技术引入到我们的自动化测试当中,想一万遍不如做一遍。,传递当前页面状态,获取之前成功的定位器路径,比较它们,并生成修复的定位器列表。如果修复成功,我们可以使用 Healenium Idea 插件更新我们的自动化测试代码:插件使用修复定位器寻找修复和更新测试代码。的旧定位器去定位按钮的时候,脚本就会报错,提示 “NoSuchElement”的错误异常。现在,我们假设开发人员变更了应用程序的UI界面,改变了这个按钮的id属性,从。

qq_58223744的博客 2660

c++学习--作为函数返回值时拷贝构造函数的流程解析

https://blog.youkuaiyun.com/pshizhsysu/article/details/40741291一个对象在创建时,一定会调用某个构造函数进行构造。如果函数的参数是以按值传递,则会调用参数类型的拷贝构造函数对形参进行构造;如果返回值是值,则会对返回给函数调用者的对象调用拷贝构造函数进行构造。看下面的使用情况[cpp] view plain copystruct A{      A(...

u010029439的博客 3733

C#上位机实战:借助NetToPLCSim与西门子S7-1200仿真通信全攻略

本文详细介绍了如何使用C#开发上位机程序,通过NetToPLCSim与西门子S7-1200进行仿真通信的全过程。从环境准备、PLC仿真环境搭建、网络配置到C#代码实现,提供了完整的实战指南和常见问题解决方案,帮助开发者快速掌握工业自动化通信技术。

weixin_30596343的博客 440

BERTopic安装指南:hdbscan依赖问题全解析(附解决方案)

本文详细解析了安装BERTopic时常见的hdbscan依赖报错问题,深入剖析了C++编译环境缺失、Python版本兼容性以及包管理渠道混用三大核心原因。针对Windows、macOS和Linux不同系统,提供了创建虚拟环境、使用conda安装预编译包、安装C++编译工具等具体解决方案,帮助用户顺利安装BERTopic并开始文本主题建模实践。

weixin_29061041的博客 213

C++构造、析构、拷贝构造、拷贝赋值、移动构造、移动赋值之二

一、概述 拷贝和移动是操作类对象的另外两种重要的方式。在类中的定义方式如下: class T{ public: T(const T&); //拷贝构造 T(T&&); //移动构造 T& operator=(const T&); //拷贝赋值 T& operator=(T&&); //移动赋值 } 在程序中发生,拷贝和移动在如下情况下默认发生

大隐隐于野 1169

【网络】Ikuai中如何添加/更换虚拟机(图文)

Ikuai 添加虚拟机教程

qq_20623849的博客 6535

C++知识点8——函数的返回值

函数的返回值不能是数组或函数,但是可以是是数组和函数的指针或引用 函数返回一个值的过程和初始化完全一致,先创建一个临时变量,用返回值初始化该临时变量,然后用该临时变量去初始化另一个变量或者给另一个变量赋值 所以函数的返回值只是返回对象的拷贝 int func() { int a=1; return a; } int b=func(); 在上面的代码中,函数并不是返回a本身,而是返回a的拷贝,这个拷贝在一个临时变量里存放,a在函数作用域结束后被释放,无效,之后用该临时变量来初.

Master Cui的博客 1783

【Docker】如何让docker容器正常使用nvidia显卡

描述了如何在docker容器中使用英伟达显卡的过程和问题解决方法。

NameLoadingFailed的博客 8372

TI官方PSpice for TI安装避坑指南:从申请Key到解决器件搜索栏空白(附详细步骤)

本文详细介绍了PSpice for TI的安装与配置全流程,包括申请Key、解决器件搜索栏空白等常见问题。通过分步指南和实战避坑技巧,帮助工程师高效完成安装并优化性能,确保电路仿真工具的正常使用。

weixin_29299807的博客 1078

关于C++函数返回值的拷贝优化问题

在传统C++程序中,如果函数的返回值是一个对象的话,可能需要对函数中的局部对象进行拷贝。如果该对象很大的话,则程序的效率会降低。在C++ 11以后,出现的移动语义(Move Semantic)及拷贝优化(Copy Elision)都是解决这个问题的方法。本文试图以一个最简单的例子来说明这个问题。

给永远比拿愉快 1054

短距激光雷达专业讲解:定义、架构、技术路线与选型逻辑

短距激光雷达,通常指有效探测距离不大于 50 米的激光雷达产品。它主要服务于服务机器人、扫地机器人、AGV/AMR、无人机、智能手机、AR/VR、工业安全等中近距感知场景。它与车载中长距激光雷达的最大区别,不是“探测距离短一点”,而是整套工程目标不同。短距激光雷达的核心诉求是:小体积、低成本、高帧率、强抗光、嵌入式集成。单价从百元级扫地机模组,到数千元级工业 3D 雷达不等。一句话概括:中长距车载激光雷达追求远距离、高可靠、车规寿命;

致力于lidar应用开发 151

C++函数返回值

首先,强调一点,和函数传参一样,函数返回时也会做一个拷贝。从某种角度上看,和传参一样,也分为三种: 一般(传统)返回:返回任意类型的数据类型,会有一个拷贝,对于复杂对象效率低下;例如:int test(){}或者 Point test(){} 返回指针:返回一个指针,也叫指针类型的函数;例如:int *test(){} 或者 Point *test(){} 返回引用:返回一个引用,也叫引用类...

赶路人儿 2万+

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

为什么可以返回临时对象?深入解析C++中的拷贝、移动与返回值优化

本文深入解析了C++中返回临时对象的底层机制。传统方式存在栈内存销毁或手动管理的问题,现代C++通过移动语义和返回值优化(RVO)实现高效安全的数据传递。关键点包括:1) 移动构造"窃取"资源,避免深拷贝;2) C++17强制拷贝省略,直接在调用位置构造对象;3) RAII机制确保资源安全转移。相比手动赋值,返回临时对象更简洁高效,符合现代C++编程范式。最佳实践是直接返回局部变量或临时对象,依赖编译器优化,避免显式拷贝或返回裸指针。

大四在找工作,如果公司有需要的可以联系我13086825462@163.com 1万+

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

【C++】返回值优化(RVO)

1. 引子 #include<iostream> using namespace std; class A { public: A() { cout << "default constructor" << endl; } A(int x) : x(x) { cout << "transform constructor" << endl; } A(const A &

明朗晨光的专栏 2442

C++函数返回值和返回引用

C++函数的返回过程基本可以分为两个阶段,返回阶段和绑定阶段,根据两个阶段中需要返回的值的类型不同(返回值和引用),和要绑定的值的类型(绑定值和引用)会产生不同的情况。 最基本的规则是先返回,再绑定,返回和绑定的时候,都有可能发生移动或者拷贝构造函数的调用来创建临时对象,并且只会发生一次。更具体的,当返回值的时候,函数返回之前,会调用一次拷贝构造函数或移动构造函数,函数在绑定到值的时候,会发生一...

gtl_csdn的博客 1万+

C++ 浅拷贝 & 深拷贝

C++ 对象的赋值, 赋值, 介绍浅拷贝 (shallow copy) 和深拷贝 (deep copy) 的区别.

我是小白呀的博客 9577

C++中拷贝构造函数的调用及返回值优化问题

关闭返回值优化: C++中临时对象及返回值优化: 关闭返回值优化以后执行以下函数: #include <iostream> #include <string.h> class student { public: // 普通构造函数 student(int i, char* c, int a, float s) { std::cout &...

qq_14950689的博客 538

C++ 返回值优化 RVO

C++ 返回值优化 RVO引子返回值优化 RVORVO 限制参考 在调试 C++ 拷贝和移动系列构造函数时候,发现构造函数调用和预期不太一样,经过查阅相关资料,发现是返回值优化(RVO)从中做梗。了解了事实真相后,今天就和大家聊聊这个 RVO。 引子 直接看一个例子: #include <iostream> class A { public: A (){ std::cout << "A(): addr= " << this << st

Dong_HFUT的博客 6525

C++标准库之string返回值研究

先说结论(不一定适用所有环境): 1)GCC默认开启了返回值优化(RVO),除非编译时指定“-fno-elide-constructors”; 2)现代C++编译器一般都支持返回值优化; 3)string的拷贝构造和拷贝赋值是浅拷贝。 测试环境: 1)gcc(GCC)4.8.5 2)g++(GCC)4.8.5 3)libstdc++.so.6.0.19 ...

一见 2720

5.深拷贝与浅拷贝+返回值优化

在C++中,深拷贝(deep copy)和浅拷贝(shallow copy)是两种不同的对象拷贝方式,它们在处理动态分配内存时表现出不同的行为。浅拷贝是指将一个对象的数据成员的值复制到另一个对象,但如果对象中包含指向动态分配内存的指针,它们将指向同一块内存空间。这样,当一个对象被销毁时,它指向的内存空间会被释放。如果另一个对象仍然引用这片内存,那么这个内存将变成悬空指针,访问它可能导致未定义行为。使用默认的拷贝构造函数和赋值运算符进行拷贝时,通常会发生浅拷贝。

weixin_62318955的博客 883

c++拷贝函数

2019.6.14 c++拷贝构造函数详解 一、什么是拷贝构造函数 首先对于普通类型的对象来说,他们之间的复制是很简单的,例如: int a = 100; int b = a; 而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变量。 下面看一个类对象拷贝的简单例子。 #include<iostream> using namespace std; clas...

sinat_39370511的博客 1万+

对C++中对象作为函数返回值时系统自动调用拷贝构造函数的理解

代码是这样的:#include &lt;iostream&gt; using namespace std; class Example { private: int test; public: Example() { cout&lt;&lt;"Constructing.."&lt;&lt;endl; } //无参构造函数 Example(in...

junlyer的博客 4034

C++拷贝构造、移动构造与返回值优化

转自:http://findingsea.github.io/2017/06/21/C-copy-constructor-move-constructor-and-return-value-optimization/?utm_source=tuicool&utm_medium=referral 拷贝构造函数 拷贝构造函数(又称复制构造函数),是用来创建已存在对象的副本。对应的还有一个概

早起的虫儿灬 1987
上一篇: 记2015年11月14日全球编程静修日(Global Day of Coderetreat)
下一篇: QtPropertyBrowser简介
e5Max
博客等级 码龄14年 145粉丝 129原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值