C++11的feature应该不能算new feature了叭,毕竟这个版本已经年代久远了。来,我们学习一下C++11的右值引用。

什么是左值,什么是右值?

lvalue这个词来自于C语言,指的是可以放在赋值表达式左边的事物——在栈上或堆上分配的命名对象,或者其他对象成员——有明确的内存地址。

rvalue这个词也来源于C语言,指的是可以出现在赋值表达式右侧的对象——例如,文字常量和临时变量。

左值引用

首先我们回顾一下年代更为久远的左值引用。

int var=42;
int& ref=var;  // 创建一个var的引用
ref=99;
assert(var==99);  // 原型的值被改变了,因为引用被赋值了 

左值引用只能被绑定在左值上,而不是右值。 因此左值引用不能这样子写:

int& i=42;  // 编译失败

不过我们可以钻空子。像这样:

int const& i = 42;

右值引用

C++11标准介绍了右值引用(rvalue reference),这种方式只能绑定右值,不能绑定左值,其通过两个&&来进行声明:

 int&& i=42; // 正确

不能绑定左值噢:

int j=42;
int&& k=j;  // 编译失败  

右值引用用途の移动语义

右值通常都是临时的,所以可以随意修改;如果知道函数的某个参数是一个右值,就可以将其看作为一个临时存储或“窃取”内容,也不影响程序的正确性。这就意味着,比起拷贝右值参数的内容,不如移动其内容。动态数组比较大的时候,这样能节省很多内存分配,提供更多的优化空间。

精简版:在传参的时候使用右值引用可以避免在内存中创建重复的变量副本,空间复杂度更低。

举个例子叭,比如老的这种写法就很耗内存:

void process_copy(std::vector<int> const& vec_)
{
  std::vector<int> vec(vec_);
  vec.push_back(42);
} 

在以上代码中,一个函数以std::vector<int>作为一个参数,就需要将其拷贝进来,而不对原始的数据做任何操作。

如果使用右值引用版本的函数来重载这个函数,就能避免在传入右值的时候,函数会进行内部拷贝的过程:

void process_copy(std::vector<int> && vec)
{
  vec.push_back(42);
} 

右值引用用途の 函数模板

如果函数模板参数以右值引用作为一个模板参数,当对应位置提供左值的时候,模板会自动将其类型认定为左值引用;当提供右值的时候,会当做普通数据使用。

我帮你整理一下思路:

if(函数模板参数以右值引用作为一个模板参数){
    if(对应位置提供左值){
        模板会自动将其类型认定为左值引用
    }
    if(对应位置提供右值){
        会当做普通数据使用
    }
}

举个栗子,定义一个函数模板:

template<typename T>
void foo(T&& t)
{} 

随后传入一个右值,T的类型将被推导为:

foo(42);  // foo<int>(42)
foo(3.14159);  // foo<double><3.14159>
foo(std::string());  // foo<std::string>(std::string()) 

不过,向foo传入左值的时候,T会被推导为一个左值引用:

int i = 42;
foo(i);  // foo<int&>(i) 

因为函数参数声明为T&&,所以就是引用的引用,可以视为是原始的引用类型。那么foo()就相当于:

foo<int&>(); // void foo<int&>(int& t); 

这就允许一个函数模板既可以即接受左值,又可以接受右值参数。

小结

我萌先回顾了左值、右值的术语概念和左值引用的语法,然后介绍了右值引用的语法,最后讲了右值引用的两种用途:移动语义和函数模板。

加油啦,你最棒!