C++中真正的临时对象是看不见的,它们不出现在你的源代码中,临时对象的产生在如下几个时刻:
1. 用构造函数作为隐式类型转换函数时,会创建临时对象。
例:
class Integer
{
public:
Integer(int i)
:m_val(i)
{}
~Integer()
{}
private:
int m_val;
};
void Calculate(Integer itgr)
{
// do something
}
那么语句: int i = 10;
Calculate(i);
会产生一个临时对象,作为实参传递到Calculate 函数中。
2. 建立一个没有命名的非堆(non-heap)对象,也就是无名对象时,会产生临时对象。
如:
Integer& iref = Integer(5); //用无名临时对象初始化一个引用,等价于
//Integer iref(5);
Integer itgr = Integer(5); //用一个无名临时对象拷贝构造另一个对象
按理说,C++应先构造一个无名的临时对象,再用它来拷贝构造itgr,由于
该临时对象拷贝构造 itgr 后,就失去了任何作用,所以对于这种类型(只起拷贝构造另一个对象的作用)的临时对象,c++特别将其看做: Integer itgr(5); 即直接以相同参数构造目标对象,省略了创建临时对象这一步。
Calculate( Integer(5) ); //无名临时对象作为实参传递给形参,函数调
//用表达式结束后,临时对象生命期结束,被//析构.
3. 函数返回一个对象值时,会产生临时对象,函数中的返回值会以值拷贝的形式拷贝到被调函数栈中的一个临时对象。
如:
Integer Func()
{
Integer itgr;
return itgr;
}
void main()
{
Integer in;
in = Func();
}
表达式 Func() 处创建了一个临时对象,用来存储Func() 函数中返回的对象,临时对象由 Func() 中返回的 itgr 对象拷贝构造(值传递),临时对象赋值给 in后,赋值表达式结束,临时对象被析构。见下图:
看看如下语句:
Integer& iRef = Func();
该语句用一个临时对象去初始化iRef 引用,一旦该表达式执行结束,临时对象的生命周期结束,便被结束,iRef引用的尸体已经不存在,接下来任何对 iRef 的操作都是错误的。
下面,来看看实际的测试结果,代码如下:
class VECTOR3
{
public:
VECTOR3()
:x(0.0f),y(0.0f),z(0.0f)
{
std::cout<<"VECTOR3 Default Constructor "
<<std::setiosflags(std::ios_base::hex)<<this
<<std::endl;
}
VECTOR3(float fx, float fy, float fz)
:x(0.0f),y(0.0f),z(0.0f)
{
std::cout<<"VECTOR3 Parameter Constructor "
<<std::setiosflags(std::ios_base::hex)<<this
<<std::endl;
}
VECTOR3(const VECTOR3& rht)
:x(rht.x), y(rht.y), z(rht.z)
{
std::cout<<"VECTOR3 Copy Constructor "
<<std::setiosflags(std::ios_base::hex)<<this
<<" from rht : "
<<std::setiosflags(std::ios_base::hex)<<&rht
<<std::endl;
}
~VECTOR3()
{
std::cout<<"VECTOR3 Destructor "
<<std::setiosflags(std::ios_base::hex)<<this
<<std::endl;
}
VECTOR3& operator = (const VECTOR3& rht)
{
if( &rht == this )
return *this;
x = rht.x;
y = rht.y;
z = rht.z;
std::cout<<"VECTOR3 operator = left oper : "
<<std::setiosflags(std::ios_base::hex)<<this
<<" right oper : "
<<std::setiosflags(std::ios_base::hex)<<&rht
<<std::endl;
return *this;
}
private:
float x;
float y;
float z;
};
VECTOR3 Func1()
{
return VECTOR3(1.0f, 1.0f, 1.0f);
}
VECTOR3 Func2()
{
VECTOR3 ret;
ret.x = 2.0f;
ret.y = 2.0f;
ret.z = 2.0f;
return ret;
}
void main()
{
VECTOR3 v1 = Func1();
v1 = Func1();
VECTOR3 v2 = Func2();
VECTOR3 v3;
v3 = Func2();
}
分析:
<1>.
VECTOR3 v1 = Func1();
该语句的执行过程本该是:
1>. 在 Func1() 中构造一个无名对象
2>. 由 Func1() 中的无名对象拷贝构造调用表达式处的临时对象
3>. 再由临时对象拷贝构造v1
4>. Func1() 返回,析构无名对象
5>. 整个语句结束,析构临时对象
但是c++ 会优化上述过程,省略了 1>. 2>. 处的临时对象创建,直接以
1.0f, 1.0f, 1.0f 为参数构造v1,这样只会有一次构造函数的调用。结果
如图:
<2>.
v1 = Func1();
该语句的执行过程本该是:
1>. 在 Func1() 中构造一个无名对象
2>. 由 Func1() 中的无名对象拷贝构造调用表达式处的临时对象
3>. 再由临时对象赋值给v1 (赋值运算符)
4>. Func1() 返回,析构无名对象
5>. 整个语句结束,析构临时对象
但是c++ 会优化上述过程,省略了 1>. 处的无名临时对象创建,直接以
1.0f, 1.0f, 1.0f 为参数构造调用表达式处的临时对象,因为是赋值,所以这个临时对象是无法被优化的,赋值完毕后,表达式结束,临时对象被析构。结果如图:
<3>.
VECTOR3 v2 = Func2();
该语句的执行过程本该是:
1>. Func2() 中的 ret 拷贝构造调用表达式处的临时对象
2>. 该临时对象拷贝构造v2
3>. 析构临时对象
但是c++ 会优化上述过程,省略了创建临时对象这一步,直接由ret拷贝
构造v2,就一次拷贝构造函数的代价。
结果如图:
<4>.
VECTOR3 v3;
v3 = Func2();
执行过程如下:
1>. 构造v3
2>. 进入Func2(),构造ret
3>. 返回ret,用ret拷贝构造到调用表达式处的临时对象
4>. Func2()结束,ret被析构
5>. 临时对象赋值给v3
6>. 赋值表达式结束,析构临时对象
结果如图:
综上所述,可得如下结论:
<1>. 在使用一个临时对象( 可能是无名对象 或者 返回对象值时 ) 创建构造另一个对象的过程的中,c++会优化掉该临时对象的产生,直接以相同参数调用相关构造函数构或者 直接调用拷贝构造函数 到 目标对象.
<2>. 若不是对象创建,而是对象赋值,则在赋值表达式的右值处的临时对象
创建不能省略,临时对象赋值给左值后,表达式结束,临时对象被析构。