C++ 中 xvalue 的 identity 存在哪里?和 lvalue 的 identity 有什么区别?

结论: identity 不是一份需要单独存放的"身份信息",而是指"这个表达式指代一个确定的对象"。xvalue 和 lvalue 同属 glvalue,在 identity 这一点上没有本质区别。

关键要分清:值类别是表达式的属性,不是对象的存储类别。

1. identity 的"内存"在哪里?

可以有两种理解:

  • 如果问的是"identity 本身存在哪里":不需要为 identity 单独分配内存,它不是对象里隐藏的某个 ID 字段。
  • 如果问的是"xvalue 指代的对象存在哪里" :由对象自身的存储期决定,与表达式是 lvalue 还是 xvalue 无关。它可能具有自动、静态、线程或动态存储期(例如通过 new 创建);也可能是一个临时对象,例如 std::move(std::string("tmp")) 中,std::string("tmp") 这个 prvalue 经过临时量实质化(C++17 起)成为临时对象,std::move 返回的 xvalue 指代的就是它。

先看指代已有对象的例子:

cpp 复制代码
#include <cassert>
#include <string>
#include <utility>

std::string s = "hello";
std::string&& r = std::move(s);

assert(&r == &s);  // 指代同一个 string 对象

这里:

表达式 值类别 指代的对象
s lvalue 对象 s
std::move(s) xvalue 仍然是对象 s
r lvalue 仍然是对象 s

注意:虽然 r 的声明类型是右值引用,但使用名字 r 这个表达式是 lvalue。

std::move(s) 没有创建新对象,没有分配内存,也没有移动任何资源,它只是一个类型转换,让同一个对象以 xvalue 表达式的形式被使用。

2. lvalue 和 xvalue 的区别是什么?

不是"身份不同",而是"如何对待这个对象"不同。

  • lvalue:通常按"这个对象还要继续使用"的方式处理,不会自动把它当作可消耗的对象。
  • xvalue:表示该对象的资源可以被重用,通过右值引用绑定、重载决议等机制,可能会选择移动操作。

例如:

cpp 复制代码
std::string a = s;             // s 是 lvalue,调用拷贝构造
std::string b = std::move(s);  // 表达式是 xvalue,调用移动构造

但 xvalue 不保证一定发生移动,还取决于类型、const 限定以及可用的构造或赋值函数。

另外,xvalue 也不意味着对象马上销毁。上面的 std::move(s) 不会缩短 s 的生命周期。

3. 移动之后,原对象的 identity 去哪里了?

没有去任何地方,原对象仍然是原对象。

执行:

cpp 复制代码
std::string b = std::move(s);

之后:

  • s 仍然存在,地址和身份没有改变。
  • b 是另一个对象,有自己的身份。
  • 移动构造可能把 s 管理的资源(例如堆上的字符缓冲区)转交给 b,而不是把 s 对象本身从原地址搬走。
  • 对 std::string 而言,移动后的 s 处于有效但未指定的状态,不能假定其内容一定是什么。

提示:主流标准库实现通常有短字符串优化(SSO),"hello" 这样的短字符串直接存放在对象内部,没有堆缓冲区。此时移动的效果更接近拷贝,s 也不一定被清空。这正是"有效但未指定"的体现,不要依赖移动后的具体内容。

也就是说,移动的是资源或状态,不是对象的 identity。

补充: 有 identity 不代表能直接写 &表达式。例如 &std::move(s) 不合法,因为内置取地址运算符要求操作数是 lvalue;但可以像前面的例子一样,通过引用 r 观察同一个对象的地址。

一句话总结

identity 回答"这是哪一个对象";lvalue / xvalue 的区别回答"这个表达式允许我们怎样使用该对象"。

核心结论(identity 不占单独内存、lvalue 和 xvalue 都是 glvalue、&std::move(s) 不合法)都是对的。

修改记录:

  1. "对象原来在哪里,就在哪里"不完整。 xvalue 不一定指代已有的具名对象,也可能指代临时对象(临时量实质化的结果)。补上了这种情况,并补全了存储期,漏了线程存储期。
  2. "会选择移动操作"说得太满。 它和后文"不保证一定发生移动"自相矛盾,改成"可能选择"。
  3. 移动资源的描述缺少前提。 "hello" 这样的短字符串在主流实现里通常用 SSO(短字符串优化),没有堆缓冲区,移动时没有"转交堆上缓冲区"这回事。补了一句说明,并把示例措辞调整得更准确。
相关推荐
SomeOtherTime4 小时前
Rust 闭包语法和原理(AI回答)
开发语言·后端·rust
apcipot_rain4 小时前
C++转Java日记(10)
java·开发语言
总要学点什么5 小时前
第105篇:三维特效-流动图片墙体效果——让箭头沿墙长滚,它俩竟是同一份代码
开发语言·javascript·ecmascript·gis·webgl·材质·cesium
YLCHUP5 小时前
【思维&图论建模】题解:债权(debt)_图论_建模_思路转化_思维题目_C++算法竞赛
c++·笔记·算法·数学建模·贪心算法·深度优先·图论
xiaoye-duck5 小时前
《C++ 项目:手写大模型接入 SDK》会话模块整合:内存 SessionManager 与 DataManager 持久化联动
开发语言·数据库·c++·语言模型
思麟呀5 小时前
背包DP(01背包+完全背包)
c++·算法
Funlang6 小时前
龙芯上的 Fun:从跑起来到机器码
开发语言·龙芯
总要学点什么6 小时前
第104篇:三维特效-流动墙体效果——让贴图沿墙流动,它却只肯走一条对角线
开发语言·javascript·ecmascript·gis·webgl·材质·cesium
旖旎夜光7 小时前
【C++ 基础入门】C++11 新特性(中):右值引用、移动语义与完美转发
开发语言·c++·笔记·学习
ShineWinsu7 小时前
对于Qt:QTabWidget常见属性的解析
开发语言·数据库·c++·qt·浏览器·qtabwidget·标签页