C++11系列之——临时对象分析

/*C++中返回一个对象时的实现及传说中的右值——临时对象*/

如下代码:

 1 /**********************************************/
 2 class CStudent;
 3 CStudent GetStudent()
 4 {
 5    CStudent loc_stu;
 6    return loc_stu;
 7 }
 8
 9 int main()
10 {
11    CStudent stu = GetStudent();
12 }
13 /**********************************************/

来看一下main函数中调用GetStudent()的汇编实现:

看上去,GetStudent()是无参函数,可是为什么在call之前push了一个参数到堆栈里呢?并且call完之后还add esp, 4来使堆栈平衡。

这个调用是这么实现的:
首先,在main函数中,stu是作为局部变量存在的,因为是个对象,会调用构造函数。
但是请注意!!!!:这里并不会在main函数中调用构造函数来构造stu,而是仅仅为stu预留了空间sizeof(CStudent)大小,并将这个空间的地址通过
mov eax, address
push eax
传递给了GetStudent()。

在GetStudent()内部定义了它的局部变量loc_stu,GetStudent()内部构造了它,在返回的时候,通过 <拷贝构造函数> 拷贝到前面提到的那个通过eax传递的隐含的参数。

总结就是说虽然stu在main里面定义,但其内存区域的内容是由GetStudent()完成的。这里需要注意的是这种调用方式,因为返回的是个对象,所以做法是调用者提前准备好一个对象空间,然后把地址告诉被调用者,被调用者得到这个地址后对其赋值,从而完成对象的返回。我姑且将这种调用方式称之为__robjcall:返回对象调用方式!呵呵

注意!!!
如果main函数改成下面这样:

/***************************************************************************/
int main()
{
   //CStudent stu = GetStudent();  原来的写法
   CStudent stu;
   stu = GetStudent();
}
/***************************************************************************/

这样先定义再赋值则大为不同!!!
stu此时作为main函数的局部变量则会调用构造函数进行初始化。另外调用GetStudent()时和上面一样,因为返回的是对象,则需要一个对象的地址,这里并不是将stu的地址交给它,因为stu已经构造好了,不能交给别的函数随意处理。而是会产生一个临时的匿名对象!!!
这个临时的匿名对象将代替之前第一种写法的做法,将自己地址交给GetStudent(),完成自己
内存的赋值。最后,通过stu的赋值操作符operator=将临时对象的值交给stu自己。再然后,临时对象完成自己的使命,立即析构,而不会等到main函数退出时。当然这个匿名的临时对象同样作为main函数的局部变量,在main函数栈帧建立时就预留了空间,这是编译器在编译的时候发现了需要一个临时对象来完成任务所以为其预留了位置。

所以,由上看出,第二种写法将增加临时对象的开销,还有进行赋值操作的开销。

如果对象比较复杂,这个开销是不能忍受的。比如下面的类型,赋值和拷贝都需要进行堆内存的操作,消耗时间。

/***************************************************************************/
class Array
{
public:
    Array(int l)                    //构造函数
    {
        pData = new int[l];
     len = l;
    }

    ~Array()                         //析构函数
    {
     delete pData;
     len = 0;
    }

    Array(const Array& other)                //拷贝构造函数
    {
     pData = new int[other.len];
     memcpy(pData, other.pData, other.len);
     len = other.len;
    }

    Array& operator=(const Array& other)          //赋值操作符
    {
     pData = new int[other.len];
     memcpy(pData, other.pData, other.len);
     len = other.len;
     return *this;
    }

private:
    int* pData;
    int len;
};
/***************************************************************************/

这便是C++中臭名昭著的临时对象性能问题!
在C++11中为了解决这个问题,引入了右值引用和move语意!
C++98规定了左值引用,如
int a;
int &b = a;
这里,b作为一个引用,引用了变量a的值,这里a是一个左值。
C++11引入了右值引用:
int &&m = 5;
这里会将5转换为一个临时对象(int型变量),然后m引用这个临时对象。
既然是右值引用,只可以引用临时对象——即右值!

这样,在C++11里,类引入了转移构造函数和转移赋值操作符,如下:

/***************************************************************************/
class Array
{
public:
    Array(int l);                          //构造函数
    ~Array();                              //析构函数
    Array(const Array& other);                //拷贝构造函数
    Array& operator=(const Array& other);     //赋值操作符

    Array(Array&& other)                   //转移构造函数
    {
     pData = other.pData;
     len = other.len;

     /*将资源转移过来,避免资源拷贝*/
     other.pData = NULL;
     other.len = 0;
    }

    Array& operator=(Array&& other)          //转移赋值操作符
    {
     pData = other.pData;
     len = other.len;

     /*将资源转移过来,避免资源拷贝*/
     other.pData = NULL;
     other.len = 0;

     return *this;
    }

private:
    int* pData;
    int len;
};

Arrar GetArray()
{
    Array loc_num(10);

    /*some operation*/

    return loc_num;
}

int main()
{
    Array num(10);
    num = GetArray();
}
/***************************************************************************/

这样,当main函数中调用GetArray()时,虽然会有一个临时匿名对象产生(前面说了,用于GetArray填充返回值用),但这里在将临时对象的内容赋值给局部对象num时,将自动识别为右值赋值,不会调用原始赋值函数Array& operator=(const Array& other);转而调用转移赋值函数Array& operator=(const Array&& other);不会进行内存二次分配,从而节省开销。

C++11系列之——临时对象分析,布布扣,bubuko.com

时间: 2024-12-17 00:52:14

C++11系列之——临时对象分析的相关文章

STL源码分析--仿函数 &amp; 模板的模板参数 &amp; 临时对象

STL源码分析-使用的一些特殊语法 关于泛型编程中用到的一些特殊语法,这些语法也适用于平常的模板编程 1.  类模板中使用静态成员变量 Static成员变量在类模板中并不是很特殊,同时这个变量不属于对象,属于实例化以后的这个类类型.每一个实例化对应一个static变量 2.  类模板中可以再有模板成员 3.  模板参数可以根据前一个模板参数而设定默认值 4.  类模板可以拥有非类型的模板参数 所谓非类型的模板参数就是内建型的模板参数 Template <class T,class Alloc =

一个函数返回临时对象引起的编译器优化问题

我们都知道,如果在一个函数调用另一个函数,假设是 main 函数调用 fun 函数,这个 fun 函数返回一个临时类类型变量,那么这个时候编译器就会在 main 函数申请一个空间并生成一个临时对象,通过拷贝构造函数将 fun 返回的临时变量的值拷贝到这个临时对象.我们看如下的代码: #include <iostream> #include <cstring> using namespace std; class Matrix { public: explicit Matrix(do

javascript系列之变量对象

原文:javascript系列之变量对象 引言 一般在编程的时候,我们会定义函数和变量来成功的构造我们的系统.但是解析器该如何找到这些数据(函数,变量)呢?当我们引用需要的对象时,又发生了什么了? 很多ECMAScript编程人员都知道变量和所处的执行上下文环境是密切相关的: 1 var a=10;//全局上下文环境下的变量 2 (function(){ 3 var b=20;//函数上下文环境下的局部变量 4 })(); 5 alert(a);//10 6 alert(b);//"b"

c++ 临时对象

我们知道在C++的创建对象是一个费时,费空间的一个操作.有些固然是必不可少,但还有一些对象却在我们不知道的情况下被创建了.通常以下三种情况会产生临时对象: 1,以值的方式给函数传参: 2,类型转换: 3,函数需要返回一个对象时: 现在我们依次看这三种情况: 一,以值的方式给函数传参. 我们知道给函数传参有两种方式.1,按值传递:2,按引用传递.按值传递时,首先将需要传给函数的参数,调用拷贝构造函数创建一个副本,所有在函数里的操作都是针对这个副本的,也正是因为这个原因,在函数体里对该副本进行任何操

临时对象与NRV技术

<More Effective C++>中讲到,在C++中真正的临时对象是看不见的,它们不出现在你的源代码中.建立一个没有命名的非堆(non-heap)对象会产生临时对象,这种未命名的对象通常在两种条件下产生:为了使函数成功调用而进行隐式类型转换和函数返回对象时. 1 size_t countChar(const string& str, char ch); 2 3 int main() 4 { 5 char c; 6 cin >> c >> setw(MAX_

C++中临时对象的学习笔记

http://www.cppblog.com/besterChen/category/9573.html 所属分类: C/C++/STL/boost 在函数调用的时候,无论是参数为对象还是返回一个对象,都将产生一个临时对象.这个笔记就是为了学习这个临时对象的产生过程而写. 本代码的详细例子见实例代码Ex.01 Ok,先让我们定义一个类: class CExample { public: int m_nFirstNum; int m_nSecNum; int GetSum(); bool SetN

More Effective C++----(19)理解临时对象的来源

Item M19:理解临时对象的来源 当程序员之间进行交谈时,他们经常把仅仅需要一小段时间的变量称为临时变量.例如在下面这段swap(交换)例程里: template<class T> void swap(T& object1, T& object2) { T temp = object1; object1 = object2; object2 = temp; } 通常把temp叫做临时变量.不过就C++而言,temp根本不是临时变量,它只是一个函数的局部对象.(一切事物皆对象

神秘的临时对象(十八)

我们在程序中不可避免的会遇到临时变量,那么在 C++ 中也会不可避免的会遇到临时对象.我们以代码为例来进行分析 #include <stdio.h> class Test { private:     int mi; public:     Test(int i)     {         mi = i;     }          Test()     {         Test(0);     }          void print()     {         printf(

C++--临时对象与经典问题

一.临时对象 Q:下面的程序将输出什么?为什么? #include <iostream> using namespace std; class Test { int mi; public: Test(int i) { mi=i; } Test() { Test(0); } void print() { cout<<"mi="<<mi<<endl; } }; int main() { Test t; t.print(); return 0;