左值引用"&"、右值引用"&&"以及它们之间的转换

来源:互联网 发布:java解压zip文件 编辑:程序博客网 时间:2024/04/30 04:14

C++11中的一个新特性就是右值引用和左右值之间的转移语义。在此之前,C++中右值引用是不被允许的。增加右值引用和转移语义这两个特性能够使代码更加简洁高效。

新特性的目的

右值引用 (Rvalue Referene) 是 C++ 新标准 (C++11, 11 代表 2011 年 ) 中引入的新特性 , 它实现了转移语义 (Move Sementics) 和精确传递 (Perfect Forwarding)。它的主要目的有两个方面:
(1) 消除两个对象交互时不必要的对象拷贝,节省运算存储资源,提高效率。
(2) 能够更简洁明确地定义泛型函数。

左值与右值的定义

C++( 包括 C) 中所有的表达式和变量要么是左值,要么是右值。通俗的左值的定义就是非临时对象,那些可以在多条语句中使用的对象。所有的变量都满足这个定义,在多条代码中都可以使用,都是左值。右值是指临时的对象,它们只在当前的语句中有效。请看下列示例 :
简单的赋值语句:
int i=233;
在这条语句中,i 是左值,233是临时值即右值。在此后的程序中,i 可以被引用,233不可以引用,并且立即数都是右值。
在 C++11 之前,右值是不能被引用的,最大限度就是用常量引用绑定一个右值,如 :

 const int &a = 1;//左值的声明符号为”&”

一般情况下,右值不能被改动。如果要改动可以这样:

F().set().get();

F是一个类,set 是一个函数为 F中的一个变量赋值,get 用来取出这个变量的值。在这句中,F() 生成一个临时对象,就是右值,set() 修改了变量的值,也就修改了这个右值。这样很麻烦,性能反而不能提升还降低了代码的简洁性。因此C++11中增加的右值引用能够方便地解决实际工程中的问题,实现非常有吸引力的解决方案。

左值和右值的语法符号

左值的声明符号为”&”, 右值的声明符号为”&&”。
示例程序 :

void processValue(int &i,int && j){    printf("%d %d\n", i,j);}void main(){    int a = 2;    processValue(a, 233);}

程序运行后输出2 233,但是如果将a 和 233调换位置,编译就会出现错误,因为非常量引用必须为左值而右值引用也无法绑定到左值。从运行的结果看,临时对象是作为右值处理的,但是如果临时对象通过一个接受右值的函数传递给另一个函数时,就会变成左值,因为这个临时对象在传递过程中,变成了命名对象。
实例程序:

void processValue(int & j){    printf("L%d", j);}void processValue(int && j){    printf("R%d", j);}void processTest(int &&i){    processValue(i);}void FunctionView(){    int a = 2;    processTest(2);}

程序运行后输出的是L2。

转移语义的定义

右值引用是用来支持转移语义的。转移语义可以将资源 ( 堆,系统对象等 ) 从一个对象转移到另一个对象,这样能够减少不必要的临时对象的创建、拷贝以及销毁,能够大幅度提高 C++ 应用程序的性能。临时对象的维护 ( 创建和销毁 ) 对性能有严重影响。
转移语义是和拷贝语义相对的,可以类比文件的剪切与拷贝,当我们将文件从一个目录拷贝到另一个目录时,速度比剪切慢很多。
通过转移语义,临时对象中的资源能够转移其它的对象里。
在现有的 C++ 机制中,我们可以定义拷贝构造函数和赋值函数。要实现转移语义,需要定义转移构造函数,还可以定义转移赋值操作符。对于右值的拷贝和赋值会调用转移构造函数和转移赋值操作符。如果转移构造函数和转移拷贝操作符没有定义,那么就遵循现有的机制,拷贝构造函数和赋值操作符会被调用。

标准库函数 std::move

标准库提供了函数 std::move,这个函数以非常简单的方式将左值引用转换为右值引用。编译器只对右值引用才能调用转移构造函数和转移赋值函数,而所有命名对象都只能是左值引用,如果已知一个命名对象不再被使用而想对它调用转移构造函数和转移赋值函数,也就是把一个左值引用当做右值引用来使用

void processValueL(int & j){    printf("L%d", j);}void processValueR(int && j){    printf("R%d", j);}void main(){    int a = 2;    processValueL(a);    processValueR(std::move(a));}

运行程序后输出L2R2。
std::move的一个重要作用就是提高数据交换的性能,使用std::move后就能够避免使用拷贝了。

void FunSwap(int a, int b){    int tmp(std::move(a));    a = std::move(b);    b = std::move(tmp);}

移动比拷贝速度要快很多,这对与那些需要大量数据交换的场合来说很有用。

总结

右值引用,表面上看只是增加了一个引用符号,但它对 C++ 软件设计和类库的设计有非常大的影响。它既能简化代码,又能提高程序运行效率。每一个 C++ 软件设计师和程序员都应该理解并能够应用它。我们在设计类的时候如果有动态申请的资源,也应该设计转移构造函数和转移拷贝函数。在设计类库时,还应该考虑 std::move 的使用场景并积极使用它。在使用之前,需要检查一下编译器的支持情况。

0 0
原创粉丝点击