【导读】:本文主要详细介绍了左值、右值、左值引用、右值引用以及move、完美转发。
左值和右值
左值(left-values),缩写:lvalues
右值(right-values),缩写:rvalues
直接上官网查,我一向倡导自己去懂得原理,而原理都是老外写的,当然我只是针对c 编程语言这样说。
https://msdn.microsoft.com/en-us/library/f90831hc.aspx
翻译:所有的c 表达,不是左值就是右值。
lvalues是指存在于单个表达式之外的对象。你可以把左值当成有名字的对象。所有的变量,包括常变量,都是左值。
rvalues是一个暂时存在的值存在于单个表达式之内的对象。
有点拗口(难理解),通俗来说就是,左值的生存期不只是这句话,后面还能用到它。
而右值呢,出了这句话就挂了,所以也叫(将亡值)。
它举了一个栗子:
#include
using namespace std;
int main() {
int x = 3 4;
cout <endl;
}
在以上实例中,很显然,x是左值,3 4是右值。
它又举了一个栗子,来说明错误的使用和正确的使用
// lvalues_and_rvalues2.cpp
int main() {
int i, j, *p;
// 正确的使用: 变量是左值
i = 7;
// 错误的使用: 左边的操作 必须是 左值 (C2106)
7 = i; // C2106
j * 4 = 7; // C2106
// 正确的使用: 被间接引用的指针是左值
*p = i;
const int ci = 7;
// 错误的使用: 左边的操作 是 常量左值 (C3892)
ci = 9; // C3892
// 正确的使用: 条件操作 返回了左值
((i 3) ? i : j) = 7;
}
左值引用、右值引用
左值引用:参考说明书《Lvalue Reference Declarator: &》,网站如下:
https://msdn.microsoft.com/en-us/library/w7049scy.aspx
使用语法:类型 &(引用符) 表达式
type-id & cast-expression
翻译:
你可以把左值引用当成对象的另一个名字,lvalue引用声明由一个可选的说明符列表和一个引用声明符组成。
引用必须初始化,而且不能改变。
一个对象的地址可以 转化成 一种指定类型的指针 或者 转化成 一个 相似类型的引用。意义是相同的。
demo:
char c_val = 'c';
char *ptr = &c_val;
char &r_val = c_val;
不要混淆 取地址 和 引用,当&说明符前面带有类型声明,则是引用,否则就是取地址。
通俗来说 &在 ”=” 号左边的是引用,右边的是取地址。
右值引用:参考说明书《Rvalue Reference Declarator: &&》,网站如下:
https://msdn.microsoft.com/en-us/library/dd293668.aspx
使用语法:类型 && 表达式
type-id && cast-expression
翻译:
Move Semantics:移动语义
右值引用使您能够区分左值和右值。Lvalue引用和rvalue引用在语法和语义上是相似的。
右值引用支持移动语义的实现,可以显著提升应用程序的性能。移动语义允许您编写将资源(例如动态分配的内存)从一个对象传输到另一个对象的代码,移动语义行之有效,因为它允许从程序中其他地方无法引用的临时对象转移资源。
为了实现移动语义,你在类中提供一个动态构造,和可选择的动态赋值运算符(operator=)。拷贝和赋值操作的资源是右值的可以自动调用移动语义。不像缺省的拷贝构造,编译器并不提供缺省的动态构造。
demo:
#include
#include
using namespace std;
int main() {
string s = string("h") "e" "ll" "o";
cout <endl;
}
在Visual C 2010之前,每个调用 “ ”运算符会分配和返回一个新的临时的string对象,
“ ”运算符不能从一个string扩展到另一个,因为它不知道string是左值还是右值。如果源字符串都是lvalues,那么它们可能在程序的其他地方被引用,因此不能被修改。通过使用右值引用“ ”运算符能够修改那些不能在程序中别处引用的右值,所以现在“ ”运算符可以有一个string扩展到另一个。这可以显著减少字符串类必须执行的动态内存分配的数量。
为了更好地理解移动语义,考虑向向量对象插入一个元素的例子。如果超出了vector对象的容量,vector对象必须为其元素重新分配内存,然后将每个元素复制到另一个内存位置,以便为插入的元素腾出空间。当插入操作复制一个元素时,它创建一个新元素,调用copy构造函数将数据从前一个元素复制到新元素,然后销毁前一个元素。移动语义允许您直接移动对象,而不必执行昂贵的内存分配和复制操作。
Perfect Forwarding:完美转发
完美的转发减少了重载函数 避免了转发的问题。转发的问题出现在你写通用函数将引用作为参数,将这些参数由函数调用的时候。
举个例子,如果通用函数将 type const T&作为参数,那么调用函数不能修改参数的值。
如果通用函数 将 type T&作为参数,那么当参数是右值的时候,函数不能调用。
通常来说,为了解决上述的问题,你需要提供重载函数,既要有type const T&参数的函数,也要有type T&参数的函数。
结果呢,重载函数的数量随着参数数量呈指数递增。而右值引用能够使你只用一个函数就能适用于任意数量的参数。
原先的做法如下:
先写出所有适用的通用函数
struct W
{
W(int&, int&) {}
};
struct X
{
X(const int&, int&) {}
};
struct Y
{
Y(int&, const int&) {}
};
struct Z
{
Z(const int&, const int&) {}
};
再将带有不同类型的参数的函数用模板结合起来
template <typename T, typename A1, typename A2>
T* factory(A1& a1, A2& a2) {
return new T(a1, a2);
}
调用:需要根据适用的类型用相应的指针对接。
当调用的是左值时
int a = 4, b = 5;
W* pw = factory(a, b);
当调用的是右值时。但是,下面的示例中没有包含对工厂函数的有效调用,因为工厂将可修改的lvalue引用作为其参数,但是它是通过使用右值调用的:
这里要注意的是const int &是lvalue 而不是 rvalue
而2是rvalue,函数会编译不过。
Z* pz = factory(2, 2);
为了解决这类问题,需要将模板函数修改成如下形式,右值引用可以适用const T& 和 T&形式的参数:
template <typename T, typename A1, typename A2>
T* factory(A1&& a1, A2&& a2) {
return new T(std::forward(a1), std::forward(a2));
}
经过上述修改,均可以调用,如下图代码所示: