C++左值引用和右值引用

1.左值和右值概念:
左值是可以放在赋值号左边可以被赋值的值:左值就是指在表达式使用完之后仍存在的对象(存储空间);
右值当在赋值号右边去除值赋给其他变量的值:右值可以在内存也可以在CPU寄存器。
可以简单的认为左值就是具有名称的对象,所有的变量(包括const修饰的变量)都是左值。而右值指的是那些在表达式使用完之后就不再存在的存储空间,实际上所有的临时变量表达式都是右值:如下解释了左值和右值的区别:

int x=3+4;//x是左值,3+4是右值,3+4的结果存放在临时对象中,在表达式之外这些空间都是不可访问的。
一个对象被用作右值时,使用的是它的内容(值),被当作左值时,使用的是它的地址。

2.引用
引用是C++语法做的优化,引用的本质还是靠指针来实现的。引用相当于变量的别名。
引用可以改变指针的指向,还可以改变指针所指向的值。
引用的基本规则:
1)声明引用的时候必须初始化,且一旦绑定,不可把引用绑定到其他对象;即引用必须被初始化,不能对引用重新定义;
2)对引用的一切操作,就相当于对原对象的操作。
3.左值引用和右值引用:
左值引用的基本语法: type &引用名=左值表达式;
右值引用的基本语法:type &&引用名= 右值表达式;
4.右值引用用来解决以下两种问题:
1)移动语义:
移动语义允许程序员将资源(比如动态分配内存)从一个对象移动到另一个对象,这将大大提高程序的性能,注意这里是移动而非复制。右值引用可以支持移动语义,是因为 它允许资源从临时对象移动出来,而初次之外是无法引用临时对象(右值)的。
当对右值进行操作时,右值本身往往没有必要保留,因此在有些情况下,右值资源可以直接移动给其他对象。通过右值引用,程序可以明确的区分出传入的参数是否为右
值,从而避免不必要的复制,因而可以提高程序的效率。下面考虑std库内的实现数据交换的swap()函数,假设X是一个已经实现备份构造函数即备份赋值运算符的类:

           template<class T>
            void swap(T& a,T%b)
            {
                T tmp(a);
                a=b;
                b=tmp;
            }
            X a,b;
            swap(a,b);

这里没有使用右值,所以swap()函数内的3行代码都没有使用移动语义,每一行都需要数据备份。
在C++0x中的std库将引入一个新的std::move()函数,这个函数只是简单的将参数转化为右值。
所以在C++0x中,std库内的swap()函数将编程类似于下面的实现:

           template<class T>
            void swap(T& a,T& b)
            {
                T tmp(std::move(a));
                a=std::move(b);
                b=std::move(tmp);
            }
            X a,b;
            swap(a,b);

通过调用std::move()函数引入右值引用,使得swap()函数的3个语句具有移动语义,这样便消除了数据备份操作,只会将源对象移动到目标对象。
2)完美转发:
完美转发技术能够有效消除函数重载的需求并避免转发问题。当编写一个将引用作为它的参数的通用函数,并且这个参数将被传递(或转发)给另一个函数时,往往会出现转发问题。比如,如果一个通用函数要求的参数类型为const T&,这意味着被调用函数不能修改该参数的值;如果通用函数要求参数类型为T&,则不能使用右值(比如临时对象或一个整型常熟)来调用。
一般情况下,为了解决这个问题,需要分别定义这个函数的两种不同重载形式,分别带T&和const T&参数,这样的后果是,随着通用函数所带参数的个数增加,需要的不同重载形式将呈指数成倍增加。而右值引用允许程序员只写一种版本的函数,就可以接受任意的参数,并将它们转发给其他的函数,就好像直接调用函数一样。
考虑下面例子中定义的4种参数类型W、X、Y、Z,它们的构造函数分别需要const及非const的左只引用作为参数:

struct W
{
    W(int&,int&){}
};
struct X
{
    X(int&,int&){}
};
struct Y
{
    Y(int&,int&){}
};
struct Z
{
    Z(int&,int&){}
};

现在假设想写一个产生对象的通用函数,其中一种写法如下:

template<typename T,typename A1,typename A2>
T* factory(A1& a1,A2& a2)
{
    return new T(a1,a2);
}

下面代码演示了对factory()函数的合法调用方式:

int a=4,b=5;
W* pw=factory<W>(a,b);

但是下面的代码种对factory的调用是非法的,因为factory()函数要求将左值引用作为参数,但却通过右值来调用:

Z* pz=factory<Z>(2,2);

通常解决这个问题的办法是,创建分别处理T&及const T&的重载函数。右值引用允许程序员只写factory()函数的一种版本,如下所示:

template<typename T,typename A1,typename A2>
T* factory(A1&& a1,A2&& a2)
{
    return new T(std::forward<A1>(a1),std::forward<A2>(a2));
}

这里使用右值引用作为factory()函数的参数,而std::forward()函数的目的就是将床底给factory()函数的参数转发给模板类的构造器。
下面的代码进一步演示了可以使用不同的方式来调用factory()函数分别产生不同类的对象,而这些调用参数总能被正确转发给合适的类构造器:

int main()
{
    int a=4,b=5;
    W* pw=factory<W>(a,b);
    X* px=factory<X>(2,b);
    Y* py=factory<Y>(a,2);
    Z* pz=factory<Z>(2,2);

    delete pw;
    delete px;
    delete py;
    delete pz;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值