本次实验环境

环境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. NC16541 [NOIP2013]车站分级

    题目链接 题目 题目描述 一条单向的铁路线上,依次有编号为1, 2, -, n 的n 个火车站.每个火车站都有一个级别,最低为1 级.现有若干趟车次在这条线路上行驶,每一趟都满足如下要求:如果这趟车次 ...

  2. 【解决方案】Java 互联网项目如何防止集合堆内存溢出(一)

    目录 前言 一.代码优化 1.1Stream 流自分页 1.2数据库分页 1.3其它思考 二.硬件配置 2.1云服务器配置 三.文章小结 前言 OOM 几乎是笔者工作中遇到的线上 bug 中最常见的, ...

  3. DS12C887时钟模块, STC89和STC12的代码实现

    DS12C887是时钟芯片DS12C885集成了电池和晶振的版本. 如果拆掉DS12C887的外壳, 能看到里面就是DS12C885. 功能特性 能输出世纪.年.月.日.时.分.秒等时间信息 集成电池 ...

  4. Ubuntu20.04安装记录

    在Ubuntu下将iso文件刻录到U盘, 可以使用系统自带的Startup Disk Creator. 分区方式 使用整个硬盘, 一个512G SSD, 使用默认的分区方式, 会创建一个512M的EF ...

  5. 【Unity3D】灯光组件Light

    1 灯光简介 ​ 在 Hierarchy 窗口右键,选择 Light,再选择具体的灯光类型,在 Inspector 窗口查看灯光组件如下: Type:灯光类型,主要有:Directional(平行光) ...

  6. 7zip 命令行压缩指定后缀名

    接到一个需求,就是测试同学在测试软件的指定功能时,可能需要调试版本来查看输出信息,所以我们需要使用一个批处理文件来快速生成一个 debug 压缩包 7zip 给出了很多有用的命令行,我们可以使用它指定 ...

  7. Flutter学习(PV)——概览

    接触flutter有一段时间了,趁着刚过完年有点时间,记录一些有用的东西,一方面给自己备忘,另一方面也希望能帮到有需要的人~ 一.什么是flutter Flutter is Google's UI t ...

  8. java基础集合类之ArrayList---01

    集合类之ArrayList ArrayList<E>: 1.可调整大小的数组实现 2.<E>:是一种特殊的数据类型,泛型 3.在出现E的地方我们使用引用数据类型替换即可:Arr ...

  9. React时间转换为具体的年月日上午下午

    export default class index extends Component { constructor() { super(); this.state = { date: new Dat ...

  10. 【Azure API 管理】APIM添加Log-to-eventhub的策略后,一些相关APIM与Event Hub的问题

    问题描述 1)    APIM 到Event Hub 写入日志是否有数量限制,比如每秒最大写入数量: 2)    是否可以在同一个APIM配置多个Event Hub,如果可以该APIM写入日志的峰值是 ...