C++关于栈对象返回的问题
本次实验环境
环境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++关于栈对象返回的问题的更多相关文章
- struts2中的值栈对象ValueStack
ValueStack, 即值栈对象. 值栈对象: 是整个struts数据存储的核心,或者叫中转站. 用户每次访问struts的action,都会创建一个Action对象.值栈对象.ActionCont ...
- Ognl值栈对象及struts标签
用户每次访问struts的action,都会创建一个Action对象.值栈对象.ActionContext对象:然后把Action对象放入值栈中: 最后再把值栈对象放入request中,传入jsp页面 ...
- 控制对象的创建方式(禁止创建栈对象or堆对象)和创建的数量
我们知道,C++将内存划分为三个逻辑区域:堆.栈和静态存储区.既然如此,我称位于它们之中的对象分别为堆对象,栈对象以及静态对象.通常情况下,对象创建在堆上还是在栈上,创建多少个,这都是没有限制的.但是 ...
- C++——内存对象 禁止产生堆对象 禁止产生栈对象
用C或C++写程序,需要更多地关注内存,这不仅仅是因为内存的分配是否合理直接影响着程序的效率和性能,更为主要的是,当我们操作内存的时候一不小心就会出现问题,而且很多时候,这些问题都是不易发觉的,比如内 ...
- Struts2_day03--课程安排_OGNL概述入门_什么是值栈_获取值栈对象_值栈内部结构
Struts2_day03 上节内容 今天内容 OGNL概述 OGNL入门案例 什么是值栈 获取值栈对象 值栈内部结构 向值栈放数据 向值栈放对象 向值栈放list集合 从值栈获取数据 获取字符串 获 ...
- 本文使用springMVC和ajax,实现将JSON对象返回到页面
一.引言 本文使用springMVC和ajax做的一个小小的demo,实现将JSON对象返回到页面,没有什么技术含量,纯粹是因为最近项目中引入了springMVC框架. 二.入门例子 ①. 建立工程, ...
- C++11用于计算函数对象返回类型的统一方法
[C++11用于计算函数对象返回类型的统一方法] 模板 std::result_of 被TR1 引进且被 C++11 所采纳,可允许我们决定和使用一个仿函数其回返值的类别.底下,CalculusVer ...
- php xml格式对象 返回->对应格式数组
/* * $objXml xml格式对象 * 返回 : 对应格式数组 */ public function XmlString2Arr($xml) { ...
- Delphi2007新功能 -- 有限的栈对象
今天使用Delphi2007,一个误输入,无意中发现Delphi2007的record类型居然能够和TObject一样定义方法和属性,而且不需要调用类似TObject.Create方法就能生成一个re ...
- Delphi栈对象
来自:http://blog.csdn.net/iseekcode/article/details/5158985 ------------------------------------------ ...
随机推荐
- NC16541 [NOIP2013]车站分级
题目链接 题目 题目描述 一条单向的铁路线上,依次有编号为1, 2, -, n 的n 个火车站.每个火车站都有一个级别,最低为1 级.现有若干趟车次在这条线路上行驶,每一趟都满足如下要求:如果这趟车次 ...
- 【解决方案】Java 互联网项目如何防止集合堆内存溢出(一)
目录 前言 一.代码优化 1.1Stream 流自分页 1.2数据库分页 1.3其它思考 二.硬件配置 2.1云服务器配置 三.文章小结 前言 OOM 几乎是笔者工作中遇到的线上 bug 中最常见的, ...
- DS12C887时钟模块, STC89和STC12的代码实现
DS12C887是时钟芯片DS12C885集成了电池和晶振的版本. 如果拆掉DS12C887的外壳, 能看到里面就是DS12C885. 功能特性 能输出世纪.年.月.日.时.分.秒等时间信息 集成电池 ...
- Ubuntu20.04安装记录
在Ubuntu下将iso文件刻录到U盘, 可以使用系统自带的Startup Disk Creator. 分区方式 使用整个硬盘, 一个512G SSD, 使用默认的分区方式, 会创建一个512M的EF ...
- 【Unity3D】灯光组件Light
1 灯光简介 在 Hierarchy 窗口右键,选择 Light,再选择具体的灯光类型,在 Inspector 窗口查看灯光组件如下: Type:灯光类型,主要有:Directional(平行光) ...
- 7zip 命令行压缩指定后缀名
接到一个需求,就是测试同学在测试软件的指定功能时,可能需要调试版本来查看输出信息,所以我们需要使用一个批处理文件来快速生成一个 debug 压缩包 7zip 给出了很多有用的命令行,我们可以使用它指定 ...
- Flutter学习(PV)——概览
接触flutter有一段时间了,趁着刚过完年有点时间,记录一些有用的东西,一方面给自己备忘,另一方面也希望能帮到有需要的人~ 一.什么是flutter Flutter is Google's UI t ...
- java基础集合类之ArrayList---01
集合类之ArrayList ArrayList<E>: 1.可调整大小的数组实现 2.<E>:是一种特殊的数据类型,泛型 3.在出现E的地方我们使用引用数据类型替换即可:Arr ...
- React时间转换为具体的年月日上午下午
export default class index extends Component { constructor() { super(); this.state = { date: new Dat ...
- 【Azure API 管理】APIM添加Log-to-eventhub的策略后,一些相关APIM与Event Hub的问题
问题描述 1) APIM 到Event Hub 写入日志是否有数量限制,比如每秒最大写入数量: 2) 是否可以在同一个APIM配置多个Event Hub,如果可以该APIM写入日志的峰值是 ...