详细大家无论是在阅读代码,还是阅读文档的时候经常看到这几个词或者对应的符号,但是可能也不是很清楚他们到底有哪些区别,本文将对这几个概念详细深入的介绍。
左值引用
左值引用的表现形式,如下所示:
int x = 2;
int& a = x; //正确,这里a就是一个左值引用
int& a = 2; //错误,临时变量2是一个右值
std::string& str = "123"; //错误,"123"是一个常量
从上面可以看到左值引用,一定是用**&**修饰的变量,且对左值引用只能使用左值来进行初始化。
这里提到了两个概念左值和右值。左值和右值相对于赋值运算符(=)而言的,=左边的就是左值;=右边以及常量等都是右值。
左值引用很常见,也很容易理解。它经常作为函数参数使用,以减少数据值拷贝的开销。
需要注意的是const修饰的左值引用是可以使用右值进行初始化,如下所示:
const int& a = 2; //正确
特点:
- 只能绑定到左值(可以取地址的、有名字的对象)
- 不能绑定到右值(临时对象)
- const 左值引用可以绑定到右值(这是一个重要的例外)
- 必须在声明时初始化右值引用
右值引用
右值引用的表现形式,如下所示:
int x = 2;
int&& a = x; //错误,a作为一个右值引用,只能使用右值进行初始化,不能绑定左值
int&& a = 2; //正确,常量是一个临时变量
右值引用使用**&&**来表示,一个变量使用&&来进行修饰,这就表明该变量是一个右值引用。
右值引用经常会使用在类定义中,如移动构造函数,见下面例子,移动构造函数的参数就是右值引用。那么如何将一个左值转换为一个右值引用呢?答案是可以通过std::move来实现。
//该构造函数为移动构造函数
class MyString { public: MyString(MyString&& other) noexcept {
// 右值引用参数
// 移动资源
data_ = other.data_;
other.data_ = nullptr; }
private: char* data_;
};
右值引用的特点:
- 只能绑定到右值
- 主要用于实现移动语义
- 可以通过 std::move 将左值转换为右值引用
- 命名的右值引用本身是左值(这是个重要概念)
万能引用
万能引用是c++11引入的概念,只有具备如下两个条件才能称作万能引用:
- 形式为T&&
- T需要进行类型推导
万能引用可以通俗的理解:万能引用既可以解释为左值引用,也可以解释为右值引用。但是不能同时被解释为左值引用和右值引用。
具体例子参见如下所示:
template<typename T>
void foo(T&& param) { // 这是万能引用
// 使用 std::forward 来保持值类别
bar(std::forward<T>(param));
}
// auto&& 也是万能引用
auto&& var = someValue;
// 这些不是万能引用,而是右值引用
void bar(Widget&& param); // 具体类型的右值引用,因为Widget不需要进行类型推导
template<typename T>
void baz(const T&& param); // const 限定符使其变成右值引用
template<typename T>
void qux(std::vector<T>&& param); // vector<T>&& 是右值引用,必须是严格的T&&形式
// 不是万能引用的情况
template<typename T>
class Container {
// 构造函数中的 T&& 不是万能引用
// 因为 T 在类模板实例化时就已确定Container(T&& value); // 这是右值引用
};
// 是万能引用的情况
template<typename T>
class Container {
// 这是万能引用,因为 U 需要推导
template<typename U>
void add(U&& value);
};
万能引用的折叠规则
首先看一个例子,如下所示:
template<typename T>
void foo(T&& param) {
// 当传入左值时:
// T 被推导为 int&
// T&& 变成 int& && -> 折叠为 int&
// 当传入右值时:
// T 被推导为 int
// T&& 保持为 int&&
}
int main() {
int x = 42;
foo(x); // 传入左值,param类型为int&
foo(42); // 传入右值,param类型为int&&
}
通过上面的例子可以看到,当传入左值时,万能引用被推导为int&,结合T&&形式变成了int& &&,这个类型折叠为int&,是一个左值引用;当传入一个右值时,万能引用被推导为int&& &&,经过折叠后变成了int &&。那么折叠的规则是什么呢?答案如下所示:
& & → & // 左值引用的左值引用 = 左值引用
& && → & // 左值引用的右值引用 = 左值引用---这个不会在万能引用中使用到
&& & → & // 右值引用的左值引用 = 左值引用
&& && → && // 右值引用的右值引用 = 右值引用-----这个不会在万能引用中使用到
关键记忆点
- 当类型中包含左值引用(&)时,折叠的结果总是左值引用(&)
- 只有当两个都是右值引用(&&)时,结果才是右值引用(&&)
- 这些规则是编译器自动应用的,我们不需要手动处理
- 正是因为有这些规则,万能引用才能正确处理各种值类别