右值引用

 

【Modern C++】深入理解左值、右值

理解 C_C++ 中的左值和右值

C++中左值(引用)及右值(引用)详解

左右值

左值

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中