标签:构造函数 源代码 析构函数 选项 编译器优化 return语句 div .com 根据
C++中真正的临时对象是看不见的,它们不出现在你的源代码中。
那么什么时候回产生临时对象呢?主要是三个时刻:
看个例子:
#include <iostream> using namespace std; #include <iostream> #include <stdlib.h> #include <string.h> using namespace std; class A{ public: int a; A(int x) { a = x; } A(const A &re ) { a = re.a; } }; int main() { A ca = 10; return 0; }
在主函数中,我们直接用一个整型量10对对象ca进行初始化,这个时候实际上应该有如下步骤:
1、 调用构造函数构造一个临时对象
2、 调用拷贝构造函数,用临时对象对ca进行初始化。
这种情况,我们和下面一种情况一起说。
看一个例子:
#include <iostream> using namespace std; #include <iostream> #include <stdlib.h> #include <string.h> using namespace std; class A{ public: int a; A(int x) { a = x; } A(const A &re ) { a = re.a; } }; A getA() { return A(200); } int main() { A ba = getA(); return 0; }
其中在getA函数中,return语句应该有如下过程
1、 A(200)会产生一个匿名的临时对象(临时对象位于函数的栈区)
2、 用拷贝构造函数,将匿名的临时对象拷贝到返回区的一个临时对象中,用于返回
函数调用时,还应该有一工程就是,调用拷贝构造函数用函数返回区的临时对象初始化ba对象。
上面就是产生临时对象的三种情况,并且我们也对相应的语句,执行的过程进行了简单的分析。
在分析的过程中,我们注意一个问题,就是,临时对象,实际上仅仅是一个桥梁作用,比如第一种情况中A ca = 10这条语句,我们的本意就是将ca对象中的成员变量初始化为10,临时变量这个东西,我们是不关注的。再比如说第二个例子中A ba = getA(),我们的目的就是用200初始化ba对象的数据成员,但这个过程中却必须要多生成两个临时对象,一个是匿名的位于函数栈区的临时对象,另一个是函数返回去的临时对象,这显然很浪费时间和空间。但是,以前的C++标准中,必须产生一个这样的临时对象。我们都知道,对象的构造和析构要涉及空间的分配和释放等,是很影响效率的。所以临时对象这东西一直被认为是影响C++效率的一个诟病。
新的C++标准中都允许对涉及临时对象的部分进行优化。当然不同的编译器有不同的优化实现方法。这次实验我用的g++
下面就来看一下。
还是看例子吧:
#include <iostream> using namespace std; #include <iostream> #include <stdlib.h> #include <string.h> using namespace std; class A{ public: int a; A(int x) { cout<<"构造函数被调用!this = "<<this<<endl; a = x; } A(const A &re ) { cout<<"拷贝构造函数被调用!this = "<<this<<endl; a = re.a; } ~A() { cout<<"析构函数被调用!\n"; } }; int main() { A ca = 10; cout<<"ca = "<<ca.a<<"地址为:"<<&ca<<endl; cout<<"---------------------------------------------\n"; return 0; }
结果为:
我们发现,和我们之前分析的不一样,如果根据我们的分析,会调用一次普通构造函数和一次拷贝构造函数,分别用于构造临时对象和用临时对象初始化ca对象。但实际结果却只调用了一次普通的构造函数。这就是编译器进行优化后的结果,而且根据两次输出的地址信息,我们可以确定,优化后是直接对ca对象进行了构造。这种优化方法叫做Copy Elision(复制的省略)
g++中可以用选项-fno-elide-constructors禁止这种优化
用该选项后,输出的结果为:
可以看到,进制优化后,和我们之前的分析就对上了。
#include <iostream> using namespace std; #include <iostream> #include <stdlib.h> #include <string.h> using namespace std; class A{ public: int a; A(int x) { cout<<"构造函数被调用!this = "<<this<<endl; a = x; } A(const A &re ) { cout<<"拷贝构造函数被调用!this = "<<this<<endl; a = re.a; } ~A() { cout<<"析构函数被调用!\n"; } }; A getA() { return A(200); } A getA2() { A temp(300); cout<<"getA2即将返回!\n"; return temp;//这里的temp就是一个将亡值 } int main() { A ba = getA(); cout<<"ba.a = "<<ba.a<<"地址为:"<<&ba<<endl; cout<<"------------------------------------\n"; A da = getA2(); cout<<"da.a = "<<da.a<<"地址为:"<<&da<<endl; cout<<"------------------------------------\n"; return 0; }
代码中有两个函数,区别是一个直接返回一个匿名的对象,另一个则是返回一个命了名的对象。
看结果:
我们可以看到,这两个函数的过程是一样的,都是直接对对象进行了构造(这一点可以根据输出的地址信息确定),而没有临时对象什么事儿。这也是编译器优化的结果,这种优化方法叫做RVO(return value optimization返回值优化),特别地,对第二个函数的返回值的优化叫做NRVO(命名返回值优化)。需要说明的是,在g++中,两个函数的优化方式相同,但是在VS中却不同(debug不同,release和g++中相同),这里就略过了。
如果禁止编译器优化,是不是结果和我们之前分析的过程相对应呢?答案是肯定的。
可以看到,都调用了两次拷贝构造函数,一次是从函数栈区的临时对象到返回区的临时对象,领一次是从返回去的临时对象到主函数中的对象。
这里还要捎带提一下,构造函数中的参数用了const修饰,这是因为,当用临时对象初始化时,会调用拷贝构造函数,而临时对象只能用const引用,这就意味着,我们不能对临时对象进行什么操作(但是在vs中竟然可以不用const修饰,不知道为什么,但是无论如何,在C++11没有引入右值引用之前,都是不能对临时对象进行修改的)。
关于右值引用,我下一篇随笔中会简要介绍一下
如果你觉得对你有用,请赞一个吧~~~
标签:构造函数 源代码 析构函数 选项 编译器优化 return语句 div .com 根据
原文地址:http://www.cnblogs.com/qingergege/p/7577216.html