C++ 左值与右值
C++ 非常注重运行效率,而 C++11 被最广泛接受的可能就是移动语义,而移动语义的基础在于区别左值表达式和右值表达式,因为一个对象是右值意味着能够对其实施移动语义,左值不可以。这里对左值右值的理解不能再以变量在表达式的左边还是右边来判定了。
判断一个表达式是左值还是右值概括来说:
左值表达式是可以对其取地址的 (形参总是左值)
右值表达式不可以(临时对象)
如果只是看明白了上面两句话就觉得已经具备足够的能力来分辨了,那可能还是有点难度的。
int a; // a 可以对其取地址,所以 a 是左值
++a; // ++a,是对 a 对象直接加1,并返回 a,依旧可以对其取地址, ++a 是左值
a++; // a++ 是对 a 拷贝的临时对象上加1,然后返回这个拷贝的临时对象,临时对象无法取地址,a++ 是右值
s1+s2; // + 操作返回的是一个临时对象,s1+s2 是右值
因为右值一般都是临时对象的形式存在,它是没有名字的,所以想找到它只能通过引用的方式,而这就引入了 C++ 中右值引用(T&&)的概念。
右值引用与万能引用
这两者概念的区别主要在于右值引用与万能引用具有相同的形式:T&&
。
右值引用仅仅会绑定到右值上。而万能引用则可以绑定到带/不带 const/volatile 修饰词的,或者同时带有 const和volatile 的左值/右值对象上。
万能引用主要在一下两个场景出现:
// 模板参数的形参
template<typename T>
void f(T&& param); // param 是个万能引用
// auto 声明
auto&& v2 = v1; // v2 是个万能引用
上述两个场景都在于它们涉及类型推导。
不涉及到类型推导的就是右值引用了:
void f(int&& param); // 右值引用
int&& v1 = 2; // 右值引用
template<typename T> void f(std::vector<T>&& param); // 右值引用,因为一定是 vector 形式的
template<typename T> void f(T&& param); // 万能引用
template<typename T> void f(const T&& param); // const 存在使 param 是右值引用
当是右值引用时,我们不能将左值传递给它:
template<typename T>
void f(std::vector<T>&& param); // 右值引用
std::vector<int> v;
f(v); //编译器会报错,不能把右值引用绑定到左值上
万能引用的初始化是必须的,其初始化会决定它是左值还是右值引用。
以上在 vector 的 push_back 的源码中就使用的 T&& 形式,vector 的类型确定下来,T 的类型就确定下来了,所以只是右值引用,而 emplace_back 就是存在类型推导,是万能引用。
左值引用与右值引用的绑定问题
左值引用和右值引用与 const 修饰词的结合共有 4 种结果:
const 左值引用 : 可以绑定 4 种引用
const 右值引用 : 只能绑定右值引用,即 const 右值引用,非const右值引用 2 种引用
非 const 左值引用 : 只能绑定 非 const 左值引用 1 种引用
非 const 右值引用 : 只能绑定 非 const 右值引用 1 种引用
首先非 const 左值引用只能绑定到非常量左值,不能绑定到其他三种类型上,因为非 const 引用没办法绑定到 const 引用上面,否则常量就有被修改的可能,这是不被允许的,而右值是临时对象,随时可能被销毁,非 const 左值引用临时对象可能会发生调用一个已经销毁了的对象的情况。