左值引用右值引用
目录
1. 什么是左值、右值
2. 什么是左值引用、右值引用
3. std::move() 移动语义
4. std::forward() 完美转发
1. 什么是左值、右值
左值可以取地址,例如位于等号左边 int a = 6 中的 a
例如变量,左值在表达式结束后依然存在
右值不能取地址,例如位于等号右边 int a = 6 中的 6
例如字面量或临时变量,通常表示一个即将被销毁的值
2. 什么是左值引用、右值引用
左值引用能指向左值,不能指向右值
例如 int &ref_a = a,而不能是 int &ref_a = 6;
因为右值没有地址,没办法被修改,而引用是变量的别名,实质上是一个变量,变量是有地址的
一个有地址,一个没地址,所以左值引用无法指向右值
但是,用const修饰后的左值引用是可以指向右值的,因为const左值引用不会修改指向值,
如 const int &ref_a = 6
这也是为什么要使用 const & 作为函数传参的原因之一
push_back(5);
void push_back(int &ref) {} // 报错
void push_back(const int &ref) {} // 不报错
右值引用的标志是 &&,两个取地址运算符,可以指向右值,不能指向左值
通过 std::move() 可以将左值转化为右值
int a = 5; // a 左值;5 右值
int &ref_l_a = a; // 左值引用
int &&ref_r_a = std::move(a); // move() 把左值转变成右值
ref_r_a = 6;
不管是左值引用还是右值引用,实质上还是引用,都可以通过取地址运算符拿到引用变量的地址
std::cout << "ref_l_a: " << &ref_l_a << std::endl; // ref_l_a: 0x7fffffffd9b4
std::cout << "ref_r_a: " << &ref_r_a << std::endl; // ref_r_a: 0x7fffffffd9b4
3. std::move() 移动语义
使用move()函数,将左值转化为右值
会触发移动构造函数,如果没有实现移动构造函数,会调用拷贝构造函数
#include <iostream>
class MyClass {
public:
// 默认构造函数
MyClass() {
std::cout << "Default 默认构造" << std::endl;
}
// 拷贝构造函数
MyClass(const MyClass &myClass) {
std::cout << "Copy 拷贝构造" << std::endl;
}
// 移动构造函数
// 如果此处不实现移动构造函数,使用move()函数的时候,会调用上面的拷贝构造函数
MyClass(MyClass &&myClass) {
std::cout << "Move 移动构造" << std::endl;
}
};
int main() {
MyClass myClass;
MyClass myClass1 = myClass; // 调用拷贝构造函数
MyClass myClass2 = std::move(myClass); // 调用移动构造函数
return 0;
}
4. std::forward() 完美转发
完美转发(Perfect Forwarding)是 C++ 中一个重要的概念
主要用于模板编程,特别是在处理函数参数时
它的核心目的是在不改变参数的值类别(左值或右值)的情况下,将参数转发给另一个函数
即传入左值就是左值,传入右值就是右值
这在性能和效率上非常重要,因为它避免了不必要的拷贝和移动操作。
函数传参 void func(T&& arg) 中 T&& arg 的理解
T&& arg 是一个万能引用,也被称为转发引用
当函数模板的参数类型是
T&&
时,T
会根据传入参数的值类别来推导:如果传入的是左值,则
T
会推导为左值引用类型;如果传入的是右值,则
T
会推导为普通类型;由于
T
的推导机制,T&& arg
可以绑定到任意类型的值,无论是左值还是右值。万能引用,或者说模板类型推导,是使用 std::forward() 的前提
#include <iostream>
#include <utility> // for std::forward
void process(int &x) { std::cout << "左值引用: " << x << std::endl; }
void process(int &&x) { std::cout << "右值引用: " << x << std::endl; }
template <typename T> void forwardToProcess(T &&arg) {
// 完美转发 可以保留传入的参数类别
process(std::forward<T>(arg));
// 如果不使用 forward() 则不管传入的是左值还是右值,在函数内部,所有参数都是以左值的形式存在
// process(arg);
}
int main() {
int a = 10;
forwardToProcess(a); // 传入左值
forwardToProcess(20); // 传入右值
return 0;
}