为什么字符串字面量是lvalue而非xvalue?
Ben Saks在2019年CppCon的《理解值类别》课程中提到,字符字符串字面量(如"examplestring")是lvalue,原因是它们本质是数组,需要存储在内存中。有人提问为何不能给字符串字面量赋值,他回应这是因为其行为类似数组,需要内存空间。针对由此产生的几个疑问,解答如下:
疑问1:如果字符串字面量是lvalue的唯一原因是需要内存存储,那它们为什么不能是临时rvalue?课程中提到过大的rvalue(如超长整数字面量)会存在内存中,但这并不意味着它们是lvalue...
C++标准对lvalue和rvalue的核心区分点不是“是否占用内存”,而是是否拥有持久的身份(identity)。字符串字面量是编译期就确定的实体,编译器会为它分配固定的内存地址,其生命周期贯穿整个程序运行期,属于有明确身份的表达式,因此是lvalue。
而超长整数字面量即便存储在内存中,也只是无身份的临时值——编译器不会为它分配固定的、可复用的地址,仅在需要时临时存储,用完即可释放,因此依然是rvalue。两者的本质差异在于是否具备持久身份,而非内存占用情况。
另外你可能混淆了“不能赋值”的原因:字符串字面量是const限定的lvalue,其类型为const char[N](N为字符串长度+1),const修饰的lvalue自然不允许被赋值,这和const int a; a=5;无法通过编译是同一个逻辑。
疑问2:为什么我可以将字符串字面量作为右值引用传递?
这是因为C++的引用折叠规则和隐式类型转换共同作用的结果:
字符串字面量是const char[N]类型的lvalue,当传递给右值引用参数时,会先触发数组到指针的隐式转换,变成const char*类型的prvalue(纯右值);而右值引用本身可以绑定到prvalue上,因此看起来像是字符串字面量直接被当作右值引用传递,本质是转换后的指针临时值满足绑定要求。
疑问3:std::string a = "string1" + "string2"; 如果"string1"和"string2"是lvalue,它们的求和结果为何是rvalue?
首先要明确:C++并没有定义两个const char*(字符串字面量会隐式转为指针)的+运算符,这条语句能编译通过,是因为至少有一个操作数被隐式转换为了std::string临时对象(前提是包含了<string>头文件)。
当其中一个字符串字面量转为std::string类型的prvalue后,std::string的operator+会返回一个新的std::string临时对象,这个临时对象是prvalue(即rvalue)。所以最终求和结果是rvalue,这和原始字符串字面量的lvalue属性并不矛盾——参与运算的已经不是原始的数组字面量,而是转换后的临时std::string对象。
关于“字符串字面量是xvalue”的误解
你认为字符串字面量是xvalue的观点存在错误:
- 字符串字面量是可以取地址的,比如:
const char* p = &"hello"[0]; // 合法,获取首字符地址 const char (*arr_ptr)[6] = &"hello"; // 合法,获取整个数组的地址 - xvalue的定义是“将亡值”,指那些即将被销毁、可以被移动的对象(比如
std::move后的对象、返回右值引用的函数返回值),而字符串字面量的生命周期和程序一致,完全不符合“将亡”的特征。
字符串字面量有固定内存地址、具备持久身份,完全符合lvalue的定义。
内容的提问来源于stack exchange,提问作者ofkilmurray

