C++中的左值引用和右值引用详解

在 C++ 中,左值引用(lvalue reference)和 右值引用(rvalue reference)是理解现代 C++ 语义的核心。它们决定了函数重载、移动语义、完美转发等特性如何工作。下面将分块来讲。


一、基本概念

引用类型语法绑定对象语义
左值引用T&可寻址的左值传统引用,不能绑定到临时对象
常量左值引用const T&左值 + 临时对象可延长临时对象寿命
右值引用T&&右值(临时对象)用于移动语义、完美转发
  • 左值(lvalue): 有名字、有地址的对象,例如变量、解引用表达式 *p、数组元素 a[i]
  • 右值(rvalue): 临时对象、字面量、运算结果等,不能取地址,如 42x+1std::string("hi")

二、区别与规则

  1. 绑定规则

    • T& 只能绑定到左值(非临时)。
    • const T& 可以绑定到左值和右值(并延长临时寿命至引用生命周期)。
    • T&& 只能绑定到右值(临时对象或 std::move(x) 的结果)。
  2. 修改权限

    • T&:可修改所引用对象。
    • const T&:不可修改。
    • T&&:可修改(通常用于“窃取”内部资源)。
  3. 重载决议

    void f(int&);    // 接收左值
    void f(int&&);   // 接收右值
    
    int x = 0;
    f(x);            // 调用 f(int&)
    f(0);            // 调用 f(int&&)
    

三、右值引用的应用

1. 移动构造与移动赋值

#include <iostream>
#include <vector>
using namespace std;

class Buffer {
    int* data;
    size_t sz;
public:
    Buffer(size_t n): sz(n), data(new int[n]) { cout<<"Ctor\n"; }
    ~Buffer(){ delete[] data; }

    // 移动构造
    Buffer(Buffer&& other) noexcept
      : data(other.data), sz(other.sz) {
        other.data = nullptr;
        other.sz = 0;
        cout<<"Move Ctor\n";
    }

    // 禁用拷贝
    Buffer(const Buffer&) = delete;
    Buffer& operator=(const Buffer&) = delete;
};

int main() {
    vector<Buffer> v;
    v.emplace_back(100);            // 构造
    v.push_back(std::move(v[0]));   // 移动构造
}
  • 意义:避免深拷贝大资源,只“窃取”指针,效率大幅提升。

2. 完美转发

#include <utility>
#include <iostream>
using namespace std;

void g(int& x)  { cout<<"lvalue g\n"; }
void g(int&& x) { cout<<"rvalue g\n"; }

template<typename T>
void wrapper(T&& t) {
    // 完美转发:保持传入 t 的值类别
    g(std::forward<T>(t));
}

int main() {
    int a = 1;
    wrapper(a);            // 输出 lvalue g
    wrapper(2);            // 输出 rvalue g
}
  • T&& 在模板参数中是 转发引用(forwarding reference),能绑定左/右值,配合 std::forward 实现完美转发。

四、常见注意事项

  1. 不要返回局部 T&&

    T&& bad() {
      T local;
      return std::move(local); // 返回悬空引用
    }
    
  2. 对左值使用 std::move 后,它变成右值引用,但原对象处于“有效但未指定”状态,谨慎再使用。

  3. 区分 T&&(右值引用)与模板中的 T&&(转发引用)

    • 非模板上下文T&& 只能绑定右值。
    • 模板上下文:当参数类型是模板参数 T&& 时,按 引用折叠规则,可绑定左右值。

五、示例汇总

#include <iostream>
#include <string>
#include <utility>
using namespace std;

void printRef(const string& s) { cout<<"lvalue ref: "<<s<<"\n"; }
void printRef(string&& s)      { cout<<"rvalue ref: "<<s<<"\n"; }

template<typename T>
void relay(T&& t) {
    printRef(std::forward<T>(t));
}

int main() {
    string s = "hello";
    printRef(s);                  // lvalue ref
    printRef("world"s);           // rvalue ref

    const string cs = "const";
    // printRef(cs);              // cs 是 const lvalue,匹配 const&

    relay(s);                     // lvalue ref
    relay("temp"s);               // rvalue ref

    // 强制将左值视为右值
    printRef(std::move(s));       // rvalue ref
}

通过掌握左值引用与右值引用,你就能:

  • 实现高效的移动语义
  • 编写通用且高性能的模板库
  • 优化函数重载,区分临时与持久对象;
  • 正确使用 std::movestd::forward,避免不必要的拷贝或悬空引用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

点云SLAM

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值