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 ------------------------------------------ ...
随机推荐
- JOISC 2023 纪录
记录一下 JOISC 2023 的做题记录 Day1 T1 Two Currencies 给定一棵树,在边上有总计 \(m\) 个检查站,经过一个检查站需要叫 \(1\) 枚金币或者若干枚银币.\(Q ...
- pt-table-checksum对比数据测试(dsns方式)
1.1.工作原理 pt-table-checksum会对校验的表数据进行数据块(数据块大小根据服务负载情况调整)划分,并对数据块数据进行checksun处理:存储在建立的表中.在主库中执行基于stat ...
- c2工具sliver的python客户端无法修改grpc超时时间的解决办法
业务需要,调用了很多implants来执行对应系统上的命令, 但是无论怎么指定interactive.py中execute方法参数, 命令执行超时时间总是30. 后面通过扩展execute方法增加一个 ...
- 案例分享:Qt工程机械真空激光焊接系统软件产品定制(西门子PLC,mysql数据库,用户权限控制,界面配置,参数定制,播放器,二维图标,rgv小车,期限控制,参数调试等)
需求 1.触摸屏控制,按照客户需求,ui由本司美工承担设计,显示分辨率1280 x 1024,同时支持鼠标操作. 2.权限控制:三种权限,分为管理员(可以定制模块界面,修改产品名称等定制化软件和 ...
- Q查询的高级用法
示例:如前端需要通过下拉框选择需要通过什么过滤字段来查询输入的关键字,后端如何使用Q查询过滤包含输入的关键字呢? def customers(request): search_field = requ ...
- iOS日志操作与开发,你真的会重视吗
iOS中常用日志和上报系统浅析 类CocoaLumberjack日志框架架构浅析 Crash的类型介绍和常用收集方案 常用上报技术方案对比和分析
- 【Azure Developer】Azure REST API: 如何通过 API查看 Recovery Services Vaults(恢复保管库)的备份策略信息? 如备份中是否含有虚拟机的Disk
问题描述 如何通过 API查看 Recovery Services Vaults(恢复保管库)的备份策略信息? 如备份中是否含有虚拟机的Disk.在Azure门户中可以通过查看Backup Item查 ...
- 【Azure Spring Cloud】Java Spring Cloud 应用部署到Azure上后,发现大量的 java.lang.NullPointerException: null at io.lettuce.core.protocol.CommandHandler.writeSingleCommand(CommandHandler.java:426) at ... 异常
Azure Spring Cloud 是什么? 借助 Azure Spring Cloud,可以轻松地将 Spring Boot 微服务应用程序部署到 Azure,不需更改任何代码. 该服务管理 Sp ...
- Jmeter Xpath提取器你了解多少?
- Java 多线程------多线程的创建,方式一:继承于Thread类
1 package com.bytezero.thread; 2 3 /** 4 * 多线程的创建,方式一:继承于Thread类 5 * 1.创建一个继承于Thread类的子类 6 * 2.重写Thr ...