解释这个之前我认为有必要解释一下什么是临时对象
什么是临时对象?定义:当且仅当离开一段上下文(context)时在对象上执行的仅有的操作是析构函数时,
一个对象被看成是临时的。临时对象很多情况下我们是看不到的 是编译器自己完成的 生存期极短
这里上下文可能是一个表达式,也可能是一个语句范围,例如函数体。
面试题中经常会考到C++的临时对象创建的相关问题 所以个人再次浅析一下三种常见的临时对象创建情况
以下仅是个人理解 如有错误 还望大家指正 //
所有代码均在xp sp3 vc++ 6.0测试通过 不同的编译器和环境结果会有所不同
1,以值的方式给函数传参; //****
2,类型转换;//****
3,函数需要返回一个对象时;//****
那么下面就让我们一个一个来看
<strong><span style="font-size:18px;"> 一,以值的方式给函数传参。//*****************//</span></strong>
我们知道函数传参有两种方式。
1,按值传递;//按值传递时,首先将需要传给函数的参数,调用拷贝构造函数创建一个副本,
//所有在函数里的操作都是针对这个副本的,也正是因为这个原因,
//在函数体里对该副本进行任何操作,都不会影响原参数。
2,按引用传递。
//引用则不然 它是对象的本身 只是一个别名而已
接着那么我们看以下例子:
#include <stdio.h>
class TempObj
{
public:
int m_Number;
int m_Size;
public:
TempObj(TempObj& to)//拷贝构造函数
{
printf("Copy function!\n");
m_Number = to.m_Number;
m_Size = to.m_Size;
};
TempObj(int m = 0,int n = 0);//带有默认参数的构造函数
~TempObj(){};//析构函数
int GetSum(TempObj ts);
};
TempObj::TempObj(int m , int n)
{
printf("Construct function!\n");
m_Number = m;m_Size=n;
printf("m_Number = %d\n",m_Number);
printf("m_Size = %d\n",m_Size);
}
int TempObj::GetSum(TempObj ts)
{
int tmp = ts.m_Number + ts.m_Size;
ts.m_Number = 1000; //此时修改的是tm的一个副本ts而已 所以不会影响对象tm
return tmp; //注意此处
}
void main()
{
TempObj tm(10,20);
printf("Sum = %d \n",tm.GetSum(tm));
printf("tm.m_Number = %d \n",tm.m_Number);
char c=getchar();
}
******************************************************************************************
输出结果:
Construct function!
m_Number = 10
m_Size = 20
Copy function!
Sum = 30
tm.m_Number = 10
请按任意键继续. . .
*******************************************************************************************
我们看到有调用了拷贝构造函数,这是tm在传给GetSum做参数时:
1,调用拷贝构造函数来创建一个副本为GetSum函数体内所用。
2,在GetSum函数体内对tm副本进行的修改并没有影响到tm本身。
解决办法:
针对第一种情况的解决办法是传入对象引用(记住:引用只是原对象的一个别名(Alias)),
我们将GetSum代码修改如下:
int TempObj ::GetSum(TempObj & ts)
{
int tmp = ts.m_Number + ts.m_Size;
ts.m_Number = 1000; //此时通过ts这个引用对象本身
return tmp;
}
*******************************************************************************************
输出结果
Construct function!
m_Number = 10
m_Size = 20
Sum = 30
tm.m_Number = 1000
请按任意键继续. . .
*******************************************************************************************
可以通过输出看通过传递常量引用,减少了一次临时对象的创建。这个改动也许很小,
但对多继承的对象来说在构建时要递归调用所有基类的构造函数,这对于性能来说是个很大的消耗,
而且这种消耗通常来说是没有必要的。
<strong><span style="font-size:18px;">
二,类型转换生成的临时对象。//****************************//</span></strong>
我们在做类型转换时,转换后的对象通常是一个临时对象。编译器为了通过编译会
创建一起我们不易察觉的临时对象。再次修改如上main代码:
void main()
{
TempObj tm(10,20),sum;
sum = 1000; //调用TempObj(int m = 0,int n = 0)的构造函数
printf("Sum = %d \n",tm.GetSum(sum));
}
*******************************************************************************************
输出结果:
Construct function!
m_Number = 10
m_Size = 20
Construct function!
m_Number = 0
m_Size = 0
Construct function!
m_Number = 1000
m_Size = 0
Sum = 1000
请按任意键继续. . .
*******************************************************************************************
main函数创建了两个对象,但输出却调用了三次构造函数,这是为什么呢?
关键在 sum = 1000;这段代码。本身1000和sum类型不符,
但编译器为了通过编译以1000为参调用构造函数创建了一下临时对象。
解决办法:
我们对main函数中的代码稍作修改,将sum申明推迟到“=”号之前:
void main()
{
TempObj tm(10,20);
TempObj sum = 1000; //调用TempObj(int m = 0,int n = 0)构造函数
printf("Sum = %d \n",tm.GetSum(sum));
}
*******************************************************************************************
输出结果:
Construct function!
m_Number = 10
m_Size = 20
Construct function!
m_Number = 1000
m_Size = 0
Sum = 1000
请按任意键继续. . .
*******************************************************************************************
只作了稍稍改动,就减少了一次临时对象的创建。
1,此时的“=”号由原本的赋值变为了构造。
2,对Sum的构造推迟了。当我们定义TempObj sum时,
在main的栈中为sum对象创建了一个预留的空间。而我们用1000调用构造时,
此时的构造是在为sum预留的空间中进行的。因此也减少了一次临时对象的创建。
<strong><span style="font-size:18px;"> 三,函数返回一个对象。 //***********************//</span></strong>
当函数需要返回一个对象,他会在栈中创建一个临时对象,存储函数的返回值。看以下代码:
#include <stdio.h>
#include <windows.h>
class TempObj
{
public:
int m_Number;
public:
TempObj(TempObj& to) //Copy Constructior Function
{
printf("Copy Constructor Function\n");
m_Number = to.m_Number;
};
TempObj& operator=(TempObj& to) //Assignment Copy Function
{
printf("Assignment Copy Function\n");
m_Number = to.m_Number;
return *this;
}
TempObj(int m = 0);
~TempObj(){};
};
TempObj::TempObj(int m)
{
printf("Construct function!\n");
m_Number = m;
printf("m_Number = %d\n",m_Number);
}
TempObj Double(TempObj& to)
{
TempObj tmp; //构建一个临时对象
tmp.m_Number = to.m_Number*2;
return tmp; //返回一个对象
}
void main()
{
TempObj tm(10),sum;
printf("\n\n");
sum = Double(tm);
printf("\n\n");
printf("sum.m_Number = %d \n",sum.m_Number);
system("pause");
}
*******************************************************************************************
输出结果:
Construct function!
m_Number = 10
Construct function!
m_Number = 0
Construct function!
m_Number = 0
Copy Constructor Function
Assignment Copy Function
sum.m_Number = 20
请按任意键继续. . .
*******************************************************************************************
sum = Double(tm);
这条语句竟生成了两个对象 我们现在将这条语句逐步分解一下:
1,我们显式创建一个tmp临时对象,
语句:TempObj tmp;
2,将temp对象返回,返回过程中调用Copy 创建一个返回对象,
语句:return tmp;
3,将返回结果通过调用赋值拷贝函数,赋给sum
语句: sum = 函数返回值;(该步并没有创建对象,只是给sum赋值)
tm.Double返回一个用拷贝构造函数生成的临时对象,并用该临时对象给sum赋值.
第2步创建的返回对象是难以避免的,你或许想可以返回一个引用,
但你别忘记了在函数里创建的局部对象,在返回时就被销毁了。
这时若再引用该对象会产生未预期的行为。
解决方法:
我们将对象直接操作返回对象,再结合上面的减少临时对象的方法,
将函数Double的代码,及main函数中的代码修改如下:
//*****************代码段1***********************
TempObj Double(TempObj& to)
{
return to.m_Number*2;
}
//******************代码段2*********************
TempObj _ret
void Double(TempObj & to)
{
_ret.m_Number = to.m_Number*2;
}
代码段1和代码段2相似
//****************************************
void main()
{
TempObj tm(10);
printf("\n\n");
TempObj sum= Double(tm);
printf("\n\n");
printf("sum.m_Number = %d \n",sum.m_Number);
}
*******************************************************************************************
输出结果:
Construct function!
m_Number = 10
Construct function!
m_Number = 20
sum.m_Number = 20
请按任意键继续. . .
*******************************************************************************************
发现减少了一次构造函数调用(tmp),一次拷贝构造函数(tmp拷贝给返回对象)调用
和一次赋值拷贝函数调用,这是因为:
返回对象直接使用为sum预留的空间,
所以减少了返回临时对象的生成——返回对象即是sum,
返回对象的创建即是sum对象的创建
浅析C++临时对象的产生相关问题
最新推荐文章于 2023-06-18 00:38:51 发布