lvalue、xvalue、prvalue(二):引用是哪一类?

一、问题



c++的引用,是prvalue还是lvalue,还是xvalue?

● xvalue(eXpiring Value)。

为什么是 xvalue?

  • prvalue 是临时值(rvalue 的一种)------右值引用的临时对象。
  • lvalue 是可取地址的持久对象。
  • xvalue 是"即将消亡的值"(即将要移动的临时对象),是 C++11 引入的第三类引用类型。

分类规则(根据 C++ 标准)

  1. lvalue:能取地址(&)、能作为左值(++、= 左侧等)。
  2. xvalue:右值引用 T&& 引用的临时对象,且即将要被移动(即将要析构)。
  3. prvalue:纯右值(临时表达式),不能取地址。

实际例子

cpp 复制代码
  class Test {
  public:
      Test() { cout << "ctor\n"; }
      ~Test() { cout << "dtor\n"; }
      Test(const Test&) { cout << "copy ctor\n"; }
      Test(Test&&) { cout << "move ctor\n"; }
  };

  void func(Test&& t) { /* t 是 xvalue */ }

  Test getTemp() { return Test(); }   // 返回临时对象 → xvalue

  int main() {
      Test t;                    // lvalue
      func(t);                   // 错误:不能把 lvalue 绑定到 T&&
      func(Test());              // ok ------ 临时对象是 xvalue
      func(getTemp());           // ok ------ getTemp() 返回 xvalue
      func(std::move(t));        // ok ------ 显式转为 xvalue
      return 0;
  }

总结一句话

  • 普通临时对象(比如 Test()、getTemp() 返回值)→ xvalue。
  • 右值引用参数接收的临时对象 → xvalue。
  • 普通变量(左值)→ lvalue。
  • 纯右值表达式(如 3 + 4、foo() + bar())→ prvalue。

如果你要区分它们,可以在代码里用 static_assert + type_traits 手动验证,或者在调试时打印 func、typeid。

二、对上面内容的分析

里面有多处关键错误 ,尤其是把 prvalue 和 xvalue 混淆了。

最根本的问题是:

T&、T&& 是引用类型;lvalue、prvalue、xvalue 是表达式的值类别。两者不是同一个概念。

所以,"引用就是 xvalue""xvalue 是第三类引用类型"都是错误说法。

1. 原例中的正确分类

表达式 正确的值类别 原因
main 中的 t lvalue 按变量名访问对象
func(Test&& t) 函数体中普通使用的 t lvalue 即使声明类型是右值引用,直接使用参数名仍是左值表达式
Test() 或 Test{} prvalue 构造该类型值的表达式
getTemp() prvalue 函数按值返回 Test
std::move(t) xvalue 将对象表达式转换成 xvalue
3 + 4 prvalue 计算得到一个值

最容易弄错的一点:右值引用变量,按名字使用时,并不因此成为右值表达式。

例如:

cpp 复制代码
Test a;
Test&& r = Test{};

// a 是 lvalue 表达式。
// r 也是 lvalue 表达式,尽管 r 的声明类型是 Test&&。

// func(a);            // 错误:不能将这里的 lvalue 绑定到 Test&&
// func(r);            // 同样错误!

func(Test{});          // 正确:实参表达式是 prvalue
func(getTemp());       // 正确:实参表达式是 prvalue
func(std::move(a));    // 正确:实参表达式是 xvalue
func(std::move(r));    // 正确:实参表达式是 xvalue

对于对象类型 T,还可以这样对照函数调用:

cpp 复制代码
T   f();   // 表达式 f() 是 prvalue
T&  g();   // 表达式 g() 是 lvalue
T&& h();   // 表达式 h() 是 xvalue

2. 为什么 Test{} 是 prvalue,却能传给 Test&&?

因为 prvalue 和 xvalue 都属于右值(rvalue),这里的右值引用可以接受它们。

在 C++17 及以后:

cpp 复制代码
func(Test{});

Test{} 原本是 prvalue 。为了绑定引用,会发生临时量实质化转换:创建临时对象,并产生指向它的 xvalue,再进行引用绑定。

"绑定过程中发生了转换",不等于"Test{} 原本就是 xvalue"。

而且,这里的引用绑定本身不会调用 Test 的移动构造函数。

3. 原文中的另外两条判断规则也不准确

xvalue 不代表对象马上析构,也不代表已经发生移动

cpp 复制代码
Test a;
std::move(a);   // 表达式是 xvalue,但这一句本身并没有移动任何东西

a 的生命周期不会因为 std::move(a) 而结束。std::move 本质上只是转换,使后续操作有机会选中移动构造、移动赋值等。

lvalue 不能简单定义成"能赋值、能取地址"

例如:

cpp 复制代码
const int n = 10;
// n = 20;     // 错误,但表达式 n 仍然是 lvalue

另外,位域表达式也可以是 lvalue,但不能对它使用内建取地址运算符。因此,"能不能赋值、取地址"不是可靠的完整分类标准。

4. 怎样用代码验证?

可以用 decltype((表达式))。对于这里的对象表达式:

  • 得到 T&:表达式是 lvalue
  • 得到 T&&:表达式是 xvalue
  • 得到非引用类型 T:表达式是 prvalue

例如,使用 C++17:

cpp 复制代码
#include <type_traits>
#include <utility>

struct Test {};
Test getTemp() { return Test{}; }

int main() {
    Test&& r = Test{};

    // 注意下面两行的区别:
    static_assert(std::is_same_v<decltype(r), Test&&>);    // r 的声明类型
    static_assert(std::is_same_v<decltype((r)), Test&>);   // r 表达式是 lvalue

    static_assert(std::is_same_v<decltype((Test{})), Test>);
    static_assert(std::is_same_v<decltype((getTemp())), Test>);
    static_assert(std::is_same_v<decltype((std::move(r))), Test&&>);
}

这里额外的括号很重要:decltype(r) 对未加括号的变量名有特殊规则,取得的是声明类型;decltype((r)) 才按表达式的值类别推导。

原文说通过 typeid 或打印 __func__ 来判断值类别,也不可靠,它们不能直接区分这三类。

一句话记忆:T&& 是引用类型;在上述例子中,r 是 lvalue,std::move(r) 是 xvalue,Test{} 是 prvalue。

相关推荐
ESDWAN41 分钟前
SD-WAN 主备切换为什么仍会断网?从检测、切换到会话恢复分析
运维·开发语言·网络·php
cxyxixiy1 小时前
数智码力:数据核对不用人工比对,Python 快速完成多表格差异校验
开发语言·python·学习
一个风轻云淡1 小时前
从 int $0x80 到 iret:用 gdb 把 getpid 走一遍
linux·c语言·开发语言·汇编
杜 硕1 小时前
从零开始的C++生活——第三讲
开发语言·c++·生活
贾伟康1 小时前
【健康菜谱助手|04】HarmonyOS ArkTS 一周膳食计划实战:管理日期、餐次与菜谱替换的联动
软件测试·移动开发·harmonyos·arkts·arkui·1024程序员节
tianbin9111 小时前
批处理与微调辅助场景:Claude 中转并发、链路缓存与压测对比
java·开发语言·缓存
frjc1 小时前
Spring AI 里 ChatClient 和 ChatModel 有什么区别?什么时候该用哪个?
java·开发语言·spring·ai
小成202303202651 小时前
C++~05异常处理
开发语言·c++
AI浩1 小时前
Xiaomi-Lens-0 技术报告
人工智能·计算机视觉·1024程序员节