浅析C++临时对象的产生相关问题

转载自:http://blog.csdn.net/microzone/article/details/6740475

解释这个之前我认为有必要解释一下什么是临时对象

   什么是临时对象?定义:当且仅当离开一段上下文(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对象的创建


阅读更多

没有更多推荐了,返回首页