C++左值(Lvalue)与右值(Rvalue)

一、左值(Lvalue)与右值(Rvalue)

1. 基本定义
  • 左值:有持久状态的对象,可以取地址,能出现在赋值号左侧。
    int a = 10;  // a是左值
    int* p = &a; // 可以取地址
    
  • 右值:临时对象,没有持久状态,不能取地址,通常出现在赋值号右侧。
    • 纯右值(prvalue):字面量、表达式结果。
      42;            // 字面量
      func();        // 返回非引用类型的函数
      a + b;         // 表达式结果
      
    • 将亡值(xvalue):通过std::move转换或返回右值引用的表达式。
      std::move(a);  // 将左值转为将亡值
      
2. 关键特性
  • 左值可绑定到左值引用(T&),右值可绑定到右值引用(T&&)。
  • 右值引用的核心用途:实现移动语义和完美转发。

二、拷贝控制:深拷贝与浅拷贝

1. 浅拷贝(Shallow Copy)
  • 按位复制对象内容,包括指针成员(仅复制指针值,不复制指向的内存)。
  • 问题:多个对象共享同一资源,易导致双重释放。
    class Shallow {
        int* data;
    public:
        Shallow(const Shallow& other) : data(other.data) {} // 浅拷贝
    };
    
2. 深拷贝(Deep Copy)
  • 复制指针指向的实际数据,每个对象拥有独立资源。
    class Deep {
        int* data;
    public:
        Deep(const Deep& other) : data(new int(*other.data)) {} // 深拷贝
    };
    

三、拷贝构造函数与移动构造函数

1. 拷贝构造函数(Copy Constructor)
  • 形式:T(const T& other)
  • 用途:创建对象的独立副本。
    MyClass(const MyClass& other) {
        data = new int(*other.data); // 深拷贝示例
    }
    
2. 移动构造函数(Move Constructor)
  • 形式:T(T&& other) noexcept
  • 用途:转移资源所有权(避免深拷贝),源对象置为空或无效状态。
    MyClass(MyClass&& other) noexcept : data(other.data) {
        other.data = nullptr; // 转移后置空源对象指针
    }
    
3. 何时触发?
  • 拷贝构造:对象初始化、传参(非移动语义)、返回对象(未优化时)。
  • 移动构造:对象初始化使用临时对象、显式std::move、返回局部对象(优化后)。

四、std::move 与移动语义

1. std::move 的作用
  • 将左值强制转换为右值引用,允许使用移动语义。
  • 注意std::move本身不移动数据,只是类型转换。
    MyClass obj1;
    MyClass obj2 = std::move(obj1); // 调用移动构造函数
    
2. 使用场景
  • 明确不再使用的对象需要转移资源时。
    void pushToVector(std::vector<MyClass>& vec, MyClass&& obj) {
        vec.push_back(std::move(obj)); // 移动而非拷贝
    }
    

五、std::forward 与完美转发

1. 完美转发(Perfect Forwarding)
  • 目标:在泛型代码中保留参数的原始值类别(左值/右值)。
  • 结合通用引用(T&&)和引用折叠规则:
    • T& &T&
    • T&& &T&
    • T& &&T&
    • T&& &&T&&
2. std::forward 的用法
  • 在模板中保持参数的值类别。
    template <typename T>
    void wrapper(T&& arg) {
        func(std::forward<T>(arg)); // 完美转发
    }
    
3. 示例对比
  • std::forward
    wrapper(42);        // arg是右值引用,但传递到func时变为左值
    
  • std::forward
    wrapper(42);        // arg保持右值属性,触发移动语义
    

六、关键总结

概念核心用途
深拷贝避免资源重复释放,独立管理资源
移动语义提升性能,转移资源所有权
std::move强制使用移动操作(左值→右值)
std::forward泛型编程中保持参数值类别(完美转发)

七、完整代码示例

#include <utility>

class Resource {
public:
    int* data;

    // 默认构造
    Resource(int val) : data(new int(val)) {}

    // 深拷贝构造
    Resource(const Resource& other) : data(new int(*other.data)) {}

    // 移动构造
    Resource(Resource&& other) noexcept : data(other.data) {
        other.data = nullptr;
    }

    ~Resource() { delete data; }
};

template<typename T>
void process(T&& arg) {
    Resource res(std::forward<T>(arg)); // 完美转发
}

int main() {
    Resource a(10);
    Resource b = a;            // 调用拷贝构造函数
    Resource c = std::move(a); // 调用移动构造函数,a.data变为nullptr

    process(Resource(20));      // 传递右值,触发移动
    process(b);                 // 传递左值,触发拷贝
}

八、注意事项

  1. 移动后的对象状态:被移动的对象应处于有效但未定义的状态(如nullptr),避免继续使用。
  2. 异常安全:移动构造函数应标记为noexcept,否则某些标准库操作(如vector扩容)可能回退到拷贝。
  3. 规则三/五原则:自定义拷贝构造/赋值时,通常需要同时定义移动操作,反之亦然。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值