参考
c++11 引入了右值引用, 它是一个非常重要的特性, 允许程序员高效地移动资源而非拷贝资源
通过右值引用, c++ 提供了一个强大的移动语义, 从而显著提高了程序的性能
概念
左值
lvalue(loactor value)
表示一个有名字变量或对象, 其存储在内存中有明确存储地址(可寻址), 可以在表达式之后仍然存在
// x是一个左值
int x = 10;
右值
rvalue(read value)
表示一个临时值, 通常没有明确内存地址, 也不会在表达式之后存在
例如, 常量10就是右值, 因为它是一个临时、没有名称值, 不会存储在命名内存位置上
// 5和3是右值, 它们和8也是一个右值
int y = 5 + 3;
判断
赋值运算符判断
任何位于赋值运算符(=)左侧的表达式都是左值位于右侧的通常是右值
int a = 5;
// 5 是右值, a 是左值
a = 5; // a 是左值, 5 是右值
名称和地址判断
左值: 有名称, 可以获取其内存地址
右值: 没有名称, 通常没有明确的内存地址
int a = 5; // a 是左值
int &b = a; // b 是左值引用
5; // 5 是右值, 没有名称
引用
左值引用
c++98/03 标准中就有引用, 使用&表示, 但此种引用方式在正常情况下只能操作 c++ 中左值, 无法对右值添加引用
int num = 10;
// 正确
int &b = num;
// 错误: 不能将右值绑定到左值引用
// 编译错误
int &c = 10;
然而, c++98/03 允许常量左值引用绑定到右值, 这使得能够在需要常量的地方使用右值
const int &c = 10; // 常量左值引用可以绑定到右值
右值引用
c++11 新标准引入右值引用, 用 && 表示, 右值引用使得可以”转移”资源, 而不是复制, 避免不必要的拷贝, 从而实现移动语义
// rvalueRef 是一个右值引用, 绑定到 10
int&& rvalueRef = 10;
右值引用必须在初始化时绑定到一个右值, 且一旦绑定, 右值引用就指向该右值
int num = 10;
// 错误: 不能将左值绑定到右值引用
int &&a = num; // 编译错误
// 正确: 右值引用绑定到右值
int &&b = 10;
应用
移动语义通过右值引用实现, 使得对象可以移动而不是拷贝, 从而提升性能
移动构造函数和移动赋值运算符是移动语义主要实现方式
移动构造函数
移动构造函数允许对象的资源从一个临时对象(右值)转移到新的对象, 而不是复制
class MyClass {
public:
int* m_data;
// 构造函数: 为 m_data 分配内存
MyClass(int value) : m_data(new int(value)) {}
// 移动构造函数: 从右值引用移动资源
MyClass(MyClass&& other) : m_data(other.m_data) {
other.m_data = nullptr; // 将 other 的资源置为空
}
// 析构函数: 释放资源
~MyClass() {
delete m_data;
}
};
移动构造函数MyClass(MyClass&& other)接收一个右值引用other, 并将其资源(m_data)转移到当前对象, 然后, 将other.mData置为空指针, 避免在析构时释放资源
移动赋值运算符
移动赋值运算符用于将一个右值的资源移动到当前对象, 避免不必要的内存分配和释放
MyClass& operator=(MyClass&& other) {
if (this != &other) {
delete m_data;
m_data = other.m_data;
other.m_data = nullptr;
}
return *this;
}
通过移动赋值运算符, 避免了不必要的资源拷贝, 从而提高了性能
std::move
std::move是一个标准库函数, 它接受一个左值并将其”转换”为右值引用, 从而可以将左值对象资源移动到另一个对象中
std::move本质上并不真正”移动”对象, 它只是将左值转换为右值引用, 使得移动语义可以生效
MyClass a(10);
// a被转换为右值引用, 资源被移动到b
MyClass b = std::move(a);
在上述代码中, std::move(a)将左值a转换为右值引用, 从而触发了移动构造函数, 将a的资源移动到b中