本次实验环境

环境1:Win10, QT 5.12

环境2:Centos7,g++ 4.8.5

一. 主要结论

  可以返回栈上的对象(各平台会有不同的优化),不可以返回栈对象的引用。

二.先看看函数传参

C++中,函数传参,可以通过值传递,指针传递,引用传递。

1) 函数参数,参数是类,通过值传递方式。下面通过代码实践一下

main()函数将生成的对象aa传入foo()函数, 相关代码如下

 1 #include <iostream>
2
3 using namespace std;
4
5 class July
6 {
7 public:
8 July()
9 {
10 cout<<"constructor "<<this<<endl;
11 }
12
13 July(const July &another)
14 {
15 cout<<"copy constructor "<<this<<" copy from "<<&another<<endl;
16 }
17
18 July & operator=(const July &another)
19 {
20 cout<<"operator = "<<this<<" copy from "<<&another<<endl;
21 }
22
23 ~July()
24 {
25 cout<<"destructor "<<this<<endl;
26 }
27 protected:
28 };
29
30 void foo(July jj)
31 {
32 cout<<"foo()"<<endl;
33 }
34
35 int main()
36 {
37 July aa;
38 foo(aa);
39 return 0;
40 }

运行结果如下

从打印结果看,发生了一次构造,一次拷贝构造,两次析构。

2) 函数参数,通过传引用方式传递参数

将上面第30行代码修改为如下

1 void foo(July &jj)

运行结果如下

从结果看来,发生了一次构造,一次析构。比上面少了一次拷贝构造和一次析构。这也验证了,传引用效率更高

三.RVO/NRVO

(具名)返回值优化((Name) Return Value Optimization,简称(N)RVO),是这么一种优化机制:当函数需要返回一个对象的时候,如果自己创建一个临时对象返回,那么这个临时对象会消耗一个构造函数的调用、一个拷贝构造函数的调用和一个析构函数的调用的代价。通过优化的方式,可以减少这些开销。Windows和Linux的RVO和NRVO是有区别的。

1) 将main()函数和foo()函数调整如下,foo()函数返回不具名临时对象

 1 July foo()
2 {
3 cout<<"foo()"<<endl;
4 return July();
5 }
6
7 int main()
8 {
9 foo();
10 return 0;
11 }

执行结果如下

从打印结果看,执行了一次构造函数和一次析构函数。

2) 去掉平台优化

在QT工程的.pro文件中,增加如下代码

1 QMAKE_CXXFLAGS += -fno-elide-constructors

保存。清除,重新构建。再次运行

从打印结果看,执行了一次构造,一次拷贝构造,两次析构。

3) 再看看具名返回值优化

将foo函数调整如下,先创建一个对象jjq,再将其返回。main函数不变,仍然是调用foo函数。

1 July foo()
2 {
3 cout<<"foo()"<<endl;
4 July jjq;
5 return jjq;
6 }

运行结果如下

从结果看,执行了一次构造,一次析构。

4) 去掉平台的优化,如上面解除优化的步骤一致

从运行结果看,执行了一次构造,一次拷贝构造,两次析构。与步骤2)中情况是一样的。

优化本质:

在main()函数调用foo()之前,会在自己的栈帧中开辟一个临时空间,该空间的地址作为隐藏参数传递给foo()函数,在需要返回A对象的时候,就在这个临时空间上构造一个A a。然后这个空间的地址再利用寄存器eax返回给main(),这样main()函数就能获得foo()函数的返回值了。

如果有人会汇编的话,可以通过反汇编观察一下调用情况。目前我不怎么会汇编,以后再抽时间看下汇编。

四.接收栈对象

接收栈对象的方式不同,会影响优化。

在没有去除平台优化的情况下,再次测试

1) foo()函数和main()函数调整如下,foo()函数中返回不具名对象,main()函数中通过foo()函数返回的对象来构造first对象

 1 July foo()
2 {
3 cout<<"foo()"<<endl;
4 return July();
5 }
6
7 int main()
8 {
9 July first = foo();
10 return 0;
11 }

运行结果如下

从打印结果看,执行了一次构造函数和一次析构函数。

2) 调整main()函数,先创建一个对象first,再将foo()函数返回的对象赋值给刚刚创建的对象first,代码如下

 1 July foo()
2 {
3 cout<<"foo()"<<endl;
4 return July();
5 }
6
7 int main()
8 {
9 July first;
10 first = foo();
11 return 0;
12 }

运行结果如下

从打印结果看,执行了两次构造函数,一次拷贝赋值函数,两次析构函数。比上面多了一次构造函数、一次拷贝赋值函数、一次析构函数。

3) foo()函数和main()函数调整如下,foo()函数中返回具名对象jjq,main()函数中通过foo()函数返回的对象来构造first对象。

 1 July foo()
2 {
3 cout<<"foo()"<<endl;
4 July jjq;
5 return jjq;
6 }
7
8 int main()
9 {
10 July first = foo();
11 return 0;
12 }

运行结果如下

执行了一次构造函数和一次析构函数。

4) 调整main()函数,先创建一个对象first,再将foo()函数返回的对象赋值给刚刚创建的对象first,代码如下

 1 July foo()
2 {
3 cout<<"foo()"<<endl;
4 July jjq;
5 return jjq;
6 }
7
8 int main()
9 {
10 July first;
11 first = foo();
12 return 0;
13 }

运行结果如下

从打印结果看,执行了两次构造函数和一次拷贝赋值函数,两次析构函数。具名与不具名是一样的情况。

所以,在接收栈对象时,直接构造新对象即可。而不必要分两步,先创建对象,再赋值,相对效率较低。

五.可否返回栈上对象的引用

其实想传达的主要是这个问题,一下子就扯了那么多。

向函数传递引用,相当于扩展了对象的作用域,使用起来比较方便。但是栈上生成的对象的引用,可以返回吗?验证一下。

main()函数和foo()函数调整如下,foo()函数返回的是引用

 1 July & foo()
2 {
3 cout<<"foo()"<<endl;
4 July jjq;
5 return jjq;
6 }
7
8 int main()
9 {
10 July first = foo();
11 return 0;
12 }

执行结果如下

从打印结果可以看到,在foo()函数中,生成的对象jjq在离开foo()函数时已经进行了析构。在main()函数中,对象first由一个空地址进行构造,这个first对象因此没有正常进行构造。没有挂掉,可能与平台有关系 。下面,把这份代码拷贝到Linux平台,再验证一下

编译时,有告警产生:"返回了局部变量的引用"。

这也说明了,可以返回栈上的对象(各平台会有不同的优化),不可以返回栈对象的引用。

参考资料

王桂林  《C++基础与提高》

C++关于栈对象返回的问题的更多相关文章

  1. struts2中的值栈对象ValueStack

    ValueStack, 即值栈对象. 值栈对象: 是整个struts数据存储的核心,或者叫中转站. 用户每次访问struts的action,都会创建一个Action对象.值栈对象.ActionCont ...

  2. Ognl值栈对象及struts标签

    用户每次访问struts的action,都会创建一个Action对象.值栈对象.ActionContext对象:然后把Action对象放入值栈中: 最后再把值栈对象放入request中,传入jsp页面 ...

  3. 控制对象的创建方式(禁止创建栈对象or堆对象)和创建的数量

    我们知道,C++将内存划分为三个逻辑区域:堆.栈和静态存储区.既然如此,我称位于它们之中的对象分别为堆对象,栈对象以及静态对象.通常情况下,对象创建在堆上还是在栈上,创建多少个,这都是没有限制的.但是 ...

  4. C++——内存对象 禁止产生堆对象 禁止产生栈对象

    用C或C++写程序,需要更多地关注内存,这不仅仅是因为内存的分配是否合理直接影响着程序的效率和性能,更为主要的是,当我们操作内存的时候一不小心就会出现问题,而且很多时候,这些问题都是不易发觉的,比如内 ...

  5. Struts2_day03--课程安排_OGNL概述入门_什么是值栈_获取值栈对象_值栈内部结构

    Struts2_day03 上节内容 今天内容 OGNL概述 OGNL入门案例 什么是值栈 获取值栈对象 值栈内部结构 向值栈放数据 向值栈放对象 向值栈放list集合 从值栈获取数据 获取字符串 获 ...

  6. 本文使用springMVC和ajax,实现将JSON对象返回到页面

    一.引言 本文使用springMVC和ajax做的一个小小的demo,实现将JSON对象返回到页面,没有什么技术含量,纯粹是因为最近项目中引入了springMVC框架. 二.入门例子 ①. 建立工程, ...

  7. C++11用于计算函数对象返回类型的统一方法

    [C++11用于计算函数对象返回类型的统一方法] 模板 std::result_of 被TR1 引进且被 C++11 所采纳,可允许我们决定和使用一个仿函数其回返值的类别.底下,CalculusVer ...

  8. php xml格式对象 返回->对应格式数组

    /*     * $objXml xml格式对象      * 返回 : 对应格式数组     */    public function XmlString2Arr($xml)    {       ...

  9. Delphi2007新功能 -- 有限的栈对象

    今天使用Delphi2007,一个误输入,无意中发现Delphi2007的record类型居然能够和TObject一样定义方法和属性,而且不需要调用类似TObject.Create方法就能生成一个re ...

  10. Delphi栈对象

    来自:http://blog.csdn.net/iseekcode/article/details/5158985 ------------------------------------------ ...

随机推荐

  1. Linux常用的20个命令(下)

    无论你是后端程序员还是前端程序员,都避免不了和Linux打交道.上篇介绍了Linux常用的20个命令其中的10个,本文继续介绍剩下的10个命令. 11.man 命令 manual的缩写,即使用手册的意 ...

  2. 内核5.4以上, Realtek 8111网卡初始化失败

    在Centos7中, 升级内核到5.4.x或5.11.x时, 都会出现realtek8111网卡无法启动的问题, 在dmesg中能看到这个错误 $ dmesg |grep -i r8169 ... r ...

  3. 【Unity3D】角色控制器(CharacterController)

    1 简介 ​ 控制角色移动的组件主要有:Transform 组件.Rigidbody 组件.CharacterController 组件.Transform 组件通过控制角色位置实现移动,Rogidb ...

  4. python中矩阵切片维数微秒变化

    1 前言 使用切片访问矩阵的部分数据(特别是一行或一列数据)时,通常会出现切片维数怎么在瞎变化,以致于不得不用reshape()强制改变维数.在深度学习中,网络对矩阵维数的要求是非常严格的,往往就是这 ...

  5. Encrypt or Decrypt sensitive data using PLSQL - DBMS_CRYPTO

    Oracle 10g introduced Transparent Data Encryption, which is about storing data physically as encrypt ...

  6. mysql存储过程实战

    今天科比离去,今天肺炎病毒持续肆虐... 意识到生命的脆弱,今天我继续前行,比以往更加坚定和紧迫,这辈子不活好自己就算白来一趟. 最近需要用到mysql存储过程去处理一些表数据,然后利用java po ...

  7. OpenCV开发笔记(六十八):红胖子8分钟带你使用特征点Flann最邻近差值匹配识别(图文并茂+浅显易懂+程序源码)

    若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...

  8. python中partial用法

    应用 典型的,函数在执行时,要带上所有必要的参数进行调用.然后,有时参数可以在函数被调用之前提前获知.这种情况下,一个函数有一个或多个参数预先就能用上,以便函数能用更少的参数进行调用. 示例pyqt5 ...

  9. mysql数据库jar包下载

    1.mysql-connector-java-8.0.16.jar驱动包 链接:https://pan.baidu.com/s/1G1SfPP895wU6YvTOAcTxhA提取码:7r43 2.my ...

  10. 【LeetCode数组#5行为模拟】螺旋矩阵II+I

    螺旋矩阵II 力扣题目链接(opens new window) 给定一个正整数 n,生成一个包含 1 到 n^2 所有元素,且元素按顺时针顺序螺旋排列的正方形矩阵. 示例: 输入: 3 输出: [ [ ...