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 ------------------------------------------ ...
随机推荐
- java 打包jar文件实战
本文只介绍实用步骤,预备知识请自查阅: 参考资料: http://docs.oracle.com/javase/tutorial/deployment/jar/appman.html http://w ...
- 使用Java语言搭建一个简易的局域网直播(live)系统
演示一下 由于是局域网直播系统,那么最简单的情况应该也有两部分构成:录制直播和播放直播. 录制直播 录制直播使用的是本机的摄像头和麦克风,使用Java自带的JFrame窗口播放,支持音视频的录制.效果 ...
- WriteFile 奇怪的现象
项目中有个需求是要对文本内容检索并重写,我们使用的是 WriteFile 覆盖旧的文本内容 最小示例: #include <Windows.h> #include <iostream ...
- webrtc 渲染音频时遇到的问题
有用户反馈连麦时,直播间会有电流声,后面排查发现是 webrtc 内部播放器渲染音频时,用户的播放设备不支持 48000hz 采样率(我们传输的音频采样率都是 48000hz),导致音频数据受损而出现 ...
- Java JVM——4.程序计数器
简介 JVM中的程序计数寄存器(Program Counter Register),Register的命名源于CPU的寄存器,寄存器存储指令相关的现场信息,CPU只有把数据装载到寄存器才能够运行. 这 ...
- .NET Core 集成微信支付签名错误
.NET Core 集成微信支付签名错误 The provided data is tagged with 'Universal' class value '16', but it should ha ...
- RocketMQ(1) 基础介绍和单机-集群安装
1. MQ简单介绍 1.1 应用场景 应用解耦 系统的耦合性越高,容错性就越低.以电商应用为例,用户创建订单后,如果耦合调用库存系统.物流系统.支付系统,任何一个子系统出了故障或者因为升级等原因暂时不 ...
- Netty笔记(3) - 核心组件
各组件关系示意图: Bootstrap 和 ServerBootstrap 说明: Bootstrap 意思是引导,一个 Netty 应用通常由一个 Bootstrap 开始,主要作用是配置整个 Ne ...
- CSV转Excel,命中关键词标红
CSV转Excel,命中关键词标红 package com.wenwo.schedule.job.mainJob; import java.io.ByteArrayOutputStream; impo ...
- Prometheus技术分享——如何监控宿主机和容器
这一期主要来跟大家聊一下,使用node_exporter工具来暴露主机和因公程序上的指标,利用prometheus来监控宿主机:以及通过通过Cadvisor监控docker容器. 一.部署node_e ...