左值与右值
左值与右值的概念
可以被取地址的值为左值(left value,简称lvalue),否则为右值(right value,简称rvalue)。
常见的左值、右值例子:
// >>>>>>> 左值
// a, p, str, str[0]等均为左值
int a = 10;
int* p = &a;
string str("hello");
str[0]// >>>>>>> 右值
// 匿名对象,临时表达式,常量10、“world”,均为右值
string("world")
x + y
10
初步认识左值和右值时,可以理解记忆为左值存储在内存中,具有“身份”,生命周期长。右值存储在寄存器中,大多为临时变量,生命周期短。
但实际上,不完全是这样:可能存在被编译器优化为存储于寄存器的左值(临时变量),也有占用空间较大,需要存储于内存的临时副本(如复杂匿名对象)。初步认识时可以帮助理解move()
将左值转换为右值的行为,类似汇编中的mov命令。
右值引用
语法为使用&&
,例如:
int&& a;
char&& b;
特性:
- 左值引用不能直接引用右值,除非加
const
限定;右值引用也不能直接引用左值,需要使用move()处理左值。 - 右值引用本身是一个左值。
移动构造
语法规则
class class_name {
public:class_name(class_name&& t) {::swap(t, t.array); // 交换资源}private:int* array;
}
意义
右值引用的概念与移动构造等特性密切相关,C++11提供了一种提高程序效率的方式,通过交换临时资源的指针代替拷贝。
对于下面这一段程序:
string func() {string tmp("1111111111111");return tmp;
}int main() {string str = func();...
}
取决于编译器的不同优化方式,C++98标准下,实际的程序会有三种行为:
- 在func内调用构造函数得到tmp,传值返回时调用构造,得到临时对象,然后拷贝赋值给str;
- 省略临时对象的构造,直接将tmp拷贝赋值给str;
- 省略tmp的构造,直接用"1111111111111"构造str;
移动构造出现后就能够替代拷贝构造,降低拷贝时的资源消耗。因为对于整个程序来说,tmp的存在只是暂时的【实际会被视为将亡值】,其资源被转移给str也无所谓,所以不用进行拷贝,直接交换资源,就能达成相同的效果。
引用折叠
基本规则
类似“&”与运算,要右值引用的右值引用最终才是右值引用。
typedef int& lref
typedef int&& rref
lref& a -> a的类型为int&
lref&& b -> b的类型为int&
rref& c -> c的类型为int&
rref&& d -> d的类型为int&&
TIP: 不能直接写int& &&
或int& &
,即不能直接对引用再做引用,必须通过typedef
自定义类型才可以,否则会语法报错。
完美引用
借用“引用折叠”的规则,可以统一模版的写法,使得最终的类型完全由实例化时的类型决定。万能模版写法:
tempate<class T>
void func(T&& a) {...
}func<int&> f1(num1) // 最终a要求为一个左值,因此num1需要是左值
func<int&&> f2(num2) // 最终a要求为一个右值,因此num2需要是右值
完美转发
forward<typename>(arg);
为避免右值被右值引用后变为左值,调用该函数,可以保持参数arg
的原始属性。
例子:
void overload(int&& rv) { std::cout << "rv " std::endl; }
void overload(int& lv) { std::cout << "lv" std::endl; }void func(int&& v) {overload(v);overload(std::forwad<int>(v));
}
- 当调用
func
时输入左值,将会得到输出:lv lv - 当调用func时输入右值,将会输出:lv rv
意义
完美引用结合完美转发,可以运用在模版的嵌套场景,可以有效避免代码冗余,同时提高代码效率。
Lambda表达式
本质
lambda表达式的本质是一个匿名函数对象,可以直接定义在函数内部。
语法
// [capture list] (parameters) -> retrun type (function body)int main() {auto test1 = [](int a) -> void {cout << a << endl;return 0;}test1(1);return 0;
}
- 捕捉列表(capture list)不能省略
- 如果参数为空,则可以省略
- 即使有返回值,返回值类型也可以省略,将会由返回对象自动推导
- 函数体不能省略(必然的)