左右值
左值
lvalue(loactor value), 表示一个有名字变量或对象, 其存储在内存中有明确存储地址(可寻址
), 可以在表达式之后仍然存在
// x是一个左值
int x = 10;
右值
rvalue(read value), 表示一个临时值, 通常没有明确内存地址, 也不会在表达式之后存在
例如, 常量10就是右值, 因为它是一个临时、没有名称值, 不会存储在命名内存位置上
// 5和3是右值, 它们和8也是一个右值
int y = 5 + 3;
引用
左值引用
C++98/03 标准中就有引用, 使用&
表示, 但此种引用方式在正常情况下只能操作 C++ 中左值, 无法对右值添加引用
int num = 10;
// 正确
int &b = num;
// 错误
int &c = 10;
编译器允许为 num 左值建立一个引用, 但不可以为 10 这个右值建立引用, 因此, C++98/03 标准中引用又称为左值引用
虽然 C++98/03 标准不支持为右值建立非常量左值引用, 但允许使用常量左值引用操作右值
常量左值引用既可以操作左值, 也可以操作右值
int num = 10;
const int &b = num;
const int &c = 10;
右值引用
C++11 新标准引入右值引用, 用 &&
表示
int&& rvalueRef = 10;
右值引用可以从字面意思上理解, 指以引用传递
(而非值传递)方式使用 C++ 右值
在 C++ 或者 C 语言中, 一个表达式(可以是字面量、变量、对象、函数返回值等) 根据其使用场景不同, 分为左值表达式和右值表达式
和声明左值引用一样, 右值引用也必须立即进行初始化操作, 且只能使用右值进行初始化
int num = 10;
const int &b = num;
const int &c = 10;
和常量左值引用不同, 右值引用还可以对右值进行修改
int num = 10;
// 错误, 右值引用不能初始化为左值
int && a = num;
// 右值引用
int && a = 10;
使用
判断
判断某个表达式是左值还是右值
赋值判断
可位于赋值运算符( = )左侧的表达式是左值; 只能位于赋值运算符右侧的表达式是右值
int a = 5;
// 错误, 5 不能为左值
5 = a;
其中, 变量 a 就是一个左值, 而字面量 5 就是一个右值
C++ 中左值也可以当做右值使用
// b 是一个左值
int b = 10;
// a、b 都是左值, 只不过将 b 可以当做右值使用
a = b;
名称判断
有名称、可以获取到存储地址的表达式即为左值, 反之则是右值
以上面变量 a、b 为例,
a 和 b 是变量名, 且通过 &a 和 &b 可以获得存储地址, 因此 a 和 b 都是左值
字面量 5、10, 它们既没有名称, 也无法获取其存储地址(字面量通常存储在寄存器中, 或者和代码存储在一起),因此 5、10 都是右值
以上 2 种判定方法只适用于大部分场景
应用
移动语义通过右值引用实现, 使得对象可以移动
而不是拷贝
, 从而提升性能
移动构造函数和移动赋值运算符是移动语义主要实现方式
移动构造函数
移动构造函数用于从右值引用”移动”一个对象资源
class MyClass {
public:
int* mData;
// 构造函数
MyClass(int value) : mData(new int(value)) {}
// 移动构造函数
MyClass(MyClass&& other) : mData(other.data) {
other.data = nullptr; // 将源对象指针置空, 避免资源释放冲突
}
// 析构函数
~MyClass() {
delete mData;
}
};
移动构造函数MyClass(MyClass&& other)接收一个右值引用other, 并将其资源(mData)转移到当前对象, 然后, 将other.mData置为空指针, 避免在析构时释放资源
移动赋值运算符
移动赋值运算符用于将一个右值引用资源”移动”到当前对象, 避免拷贝
MyClass& operator=(MyClass&& other) {
if (this != &other) {
delete mData; // 释放当前对象资源
mData = other.mData; // 转移资源
other.mData = nullptr; // 防止释放资源
}
return *this;
}
这个移动赋值运算符避免不必要内存分配和释放, 提升了效率
std::move
std::move是一个标准库函数, 它接受一个左值并将其”转换”为右值引用, 从而可以将左值对象资源移动到另一个对象中
注意, std::move本质上并不真正”移动”对象, 它只是将左值转换为右值引用, 使得移动语义可以生效
MyClass a(10);
MyClass b = std::move(a); // a被转换为右值引用, 资源被移动到b
在上述代码中, std::move(a)将左值a转换为右值引用, 从而触发了移动构造函数, 将a的资源移动到b中