ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

复杂的C++,当函数返回对象到底发生了什么

2021-03-05 19:01:44  阅读:153  来源: 互联网

标签:返回 函数 对象 t2 C++ t1 编译器 test 构造函数


我们知道,当函数运行结束的时候,函数内部的局部变量就会消失,这C/C++里没有任何疑问的规定,但是今天我在写代码的时候突然就想到了一个相当纠结的问题,那就是当我一个函数返回类型是一个对象的时候,以我当时掌握的知识理解,当函数返回时回要生成一个临时对象,这个临时对象可能会开销很多资源,那么这样我们的函数就不能设计成一个返回类型为对象的函数了,或者想办法避免产生这个临时对象,方法就是使用动态分配内存。出于验证的想法,我写了下面一段代码进行测试:

#include <iostream>
using namespace std;
struct Test{
    Test() {cout<<"Constructor"<<endl;}
    Test(const Test &){cout<<"copy Constructor"<<endl;}
    ~Test() {cout<<"destroy"<<endl;}
};
Test fun()
{
    Test t1;
    cout<<"&t1 is "<<&t1<<endl;
    return t1;
}
int main()
{
    fun();
    cout<<"This is a test!"<<endl;
    return 0;
}

对于上面的代码,我的思考是这样的:
首先调用函数fun(),调用构造函数产生t1,输出t1的地址,到达return t1语句,这时调用拷贝构造函数产生一个临时对象,t1析构,临时对象析构,输出”This is a test!”,程序结束。

没错吧?对于上面的思考过程,如果你以前对C++有深入的了解,对这样的思考过程应该是认同的吧

好,我们用g++编译运行一下,我的g++版本是gcc version 4.8.4 (Ubuntu 4.8.4-2ubuntu1~14.04.3),下面是运行结果

Constructor
&t1 is 0x7ffce5a4956f
destroy
This is a test!

不对吧?从这个结果看,好像没有产生临时对象啊,难道是我错了?还是说g++发现我没有使用函数的返回值然后帮我优化了?OK,我就修改一下代码:

int main()
{
    Test t2 = fun();
    cout<<"&t2 is "<<&t2<<endl;
    cout<<"This is a test!"<<endl;
    return 0;
}

如果的想法是没有错的,那么过程应该是这样的:
– 首先调用函数fun(),调用构造函数产生t1,输出t1的地址,到达return t1语句,这时调用拷贝构造函数产生一个临时对象,t1析构,调用拷贝构造函数产生对象t2,临时对象析构,输出t2的地址,输出”This is a test!”,t2析构,程序结束。

实际用g++编译运行的结果是:

Constructor
&t1 is 0x7fff5303673f
&t2 is 0x7fff5303673f
This is a test!
destroy

妈呀!这是什么情况?我怎么有点懵逼啊?从这个结果看,只在fun()里调用了一次构造函数,产生t1对象,没有产生临时对象就算了,把我的t2都搞没了是怎么回事?不对,发现一个新问题,t1对象的析构函数怎么变成了在程序结束的时候才调用?不应该是函数fun()结束后就调用的吗?难道又是编译器给优化了?我把优化选项关了,编译时采用的是-O0参数,不过结果还是没有调用拷贝构造函数产生t2,这里我对优化选项了解得不深,所以我采用了代码进行另外的测试。

带着新问题,我又继续写了几段测试代码,测试为什么没有产生t1对象,发现真的是编译器进行了优化(可见-O0选项其实也是对性能进行了一定优化的),我们看看下面这段,编译器不可以采用优化的:

Test fun2(Test const &t)
{
    cout<<"&t3 is "<<&t3<<endl;
    return t;
}
int main()
{
    const Test t3;
    Test t2 = fun2(t3);
    cout<<"&t2 is "<<&t2<<endl;
    cout<<"This is a test!"<<endl;
    return 0;
}

运行的结果是:

Constructor
&t3 is 0x7ffec045a2be
copy Constructor
&t2 is 0x7ffec045a2bf
This is a test!
destroy
destroy

没错,这里调用拷贝构造函数产生了t2了,是编译器优化了之前的那段代码,把原本应该在fun()结束时就析构的t1继续当成了t2使用,提高了效率。

好了,我们回到一开始的问题了,按照之前的理解,函数返回应该是要产生一个临时对象的,我记得在《C++Primer》中都是有提及的,带着这个疑问,我又继续百度了,果然证明我一开始的认知是没有错的,传送门,这是一种叫返回值优化的机制(Return Value Optimization,简称RVO),具体有兴趣的同学去仔细看看。

原来就是g++编译器帮我们优化,以提高性能的,我们可以通过在编译的时候,加上-fno-elide-constructors这个选项,来去除这个优化(再次证明我对g++优化没有一个系统的学习)

我们重新编译最开始的那段程序

g++ -o test test.cpp -fno-elide-constructors

运行:

./test

运行结果:

Constructor
&t1 is 0x7ffeaf3d693f
copy Constructor
destroy
destroy
This is a test!

这下没错了,完全符合一开始的猜想,不过这种产生临时对象的做法效率实在会大大降低,所以g++优化得好啊,不过我们还是要知道编译器帮我们干了这么一回事,以防有什么特殊情况。

写到这里,我又想说了,可能有的人认为不用如此纠结这种问题,但我觉得如果想用真正精通C++,那么我们就必须知道编译器纠结都干了什么,C++之所以那么复杂,那么难,就是因为编译器会帮我们干一些我们可能不知道的事情。

标签:返回,函数,对象,t2,C++,t1,编译器,test,构造函数
来源: https://blog.csdn.net/Fei20140908/article/details/114408178

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有