Nat*_*ica 20
而且文字和临时变量也不是左值,但没有给出这个陈述的理由.
对于除字符串文字之外的所有临时文字和文字都是如此.这些实际上是左值(将在下面解释).
是因为文字和临时变量没有定义的存储位置?如果是的话,那么如果不在记忆中它们会在哪里居住?
是.文字2实际上并不存在; 它只是源代码中的一个值.由于它是一个值,而不是一个对象,因此它不必具有任何与之关联的内存.它可以硬编码到编译器创建的程序集中,也可以放在某个地方,但由于它不是必须的,所以你所能做的只是将它视为纯值,而不是对象.
虽然有一个豁免,那就是字符串文字.那些实际上有存储,因为字符串文字是一个数组const char[N].您可以获取字符串文字的地址,字符串文字可以衰减为指针,因此它是左值,即使它没有名称.
临时也是左撇子.即使它们作为对象存在,它们的存储位置也是短暂的.它们只会持续到它们所处的完整表达结束.您不能使用他们的地址而且他们也没有名字.它们甚至可能不存在:例如,在
Foo a = Foo();
Run Code Online (Sandbox Code Playgroud)
所述Foo()可以被移除和代码语义转换为
Foo a(); // you can't actually do this since it declares a function with that signature.
Run Code Online (Sandbox Code Playgroud)
所以现在优化代码中甚至没有临时对象.
为什么文字和临时变量不是左值?
我有两个答案:因为它没有意义(1),因为标准说的是(2).让我们关注(1).
是因为文字和临时变量没有定义的存储位置?
这是一个不适合的简化.简化:文字和临时不是左值,因为修改它们没有意义1.
是什么意思5++?是什么意思rand() = 0?标准说临时文字和文字不是左值,所以那些例子是无效的.每个编译器开发人员都更开心.
1)您可以以修改临时的方式定义和使用用户定义的类型.这个临时表决直到评估完整表达.弗朗索瓦·安德里乌斯(FrançoisAndrieux)f(MyType{}.mutate())在一只手和另一只手之间做了一个很好的类比f(my_int + 1).我想简化持有仍MyType{}.mutate()可以看作是另一种暂时的MyType{}是,像my_int + 1可以看作是另一种int为my_int是.这是所有语义和基于意见的.真正的答案是:(2)因为标准这样说.
在问题和其他答案中存在许多常见的误解; 我的回答希望解决这个问题.
术语lvalue和rvalue是表达类别.它们是适用于表达式的术语.不是对象.(有点令人困惑的是,表达类别的官方术语是"价值类别"!)
术语临时对象是指对象.这包括类类型的对象,以及内置类型的对象.术语临时(用作名词)是临时对象的缩写.有时,独立术语值用于指代内置类型的临时对象.这些术语适用于对象,而不适用于表达式.
与过去的标准相比,C++ 17标准在对象术语中更加一致,例如参见[conv.rval]/1.它现在试图避免在表达式的上下文值之外说出值.
现在,为什么有不同的表达类别?C++程序由一组表达式组成,它们与运算符相互连接,以生成更大的表达式; 并在声明性结构的框架内拟合.这些表达式创建,销毁和对对象进行其他操作.用C++编程可以描述为使用表达式来执行对象操作.
表达式类别存在的原因是提供一个框架,用于使用表达式来表达程序员想要的操作.例如,回到C日(可能更早),语言设计师认为3 = 5;作为程序的一部分没有任何意义,因此决定限制在左侧可以出现的表达式=,以及如果未遵循此限制,则让编译器报告错误.
术语" 左值"起源于那些日子,尽管现在随着C++的发展,表达类别很有用,而不仅仅是赋值运算符的左侧.
这是一些有效的C++代码:std::string("3") = std::string("5");.这在概念上没有区别3 = 5;,但允许.结果是创建了类型std::string和内容的临时对象"3",然后修改该临时对象以获取内容"5",然后销毁临时对象.可以设计该语言,以便代码3 = 5;指定一系列类似的事件(但事实并非如此).
为什么string示例合法,但int示例不是?
每个表达都必须有一个类别.表达的类别一开始似乎没有明显的原因,但语言的设计者根据他们认为有用的概念表达和不表达的内容给每个表达一个类别.
已经确定3 = 5;上面描述的事件序列不是任何人想要做的事情,如果有人写了这样的东西,那么他们可能犯了一个错误并且意味着别的东西,所以编译器应该通过给出错误来帮助信息.
现在,同样的逻辑可能会得出结论,这std::string("3") = std::string("5")不是任何人都想做的事情.然而另一个论点是,对于某些其他类类型,T(foo) = x;实际上可能是一个有价值的操作,例如因为T可能有一个析构函数可以执行某些操作.决定禁止这种用法可能对程序员的意图更有害,而不是好处.(这是否是一个好的决定是值得商榷的; 请参阅此问题进行讨论).
现在我们越来越接近终于解决你的问题了:)
是否存在关联的存储器或存储位置不再是表达类别的基本原理.在抽象机器中(下面对此进行更多解释),每个临时对象(包括由3in 创建的对象x = 3;)都存在于内存中.
正如我在前面的回答中所描述的,程序由操纵对象的表达式组成.据说每个表达式指定或引用一个对象.
关于这个主题的其他答案或文章很常见的是,rvalue只能指定一个临时对象,或者更糟糕的是,rvalue 是一个临时对象,或者一个临时对象是一个rvalue.表达式不是对象,它是源代码中用于操作对象的东西!
实际上,临时对象可以由左值或右值表达式指定; 并且可以通过左值或右值表达式指定非临时对象.它们是不同的概念.
现在,有一个表达式类别规则,您无法应用于&右值类别的表达式.此规则和这些类别的目的是避免在销毁临时对象后使用临时对象的错误.例如:
int *p = &5; // not allowed due to category rules
*p = 6; // oops, dangling pointer
Run Code Online (Sandbox Code Playgroud)
但你可以解决这个问题:
template<typename T> auto f(T&&t) -> T& { return t; }
// ...
int *p = f(5); // Allowed
*p = 6; // Oops, dangling pointer, no compiler error message.
Run Code Online (Sandbox Code Playgroud)
在后面的代码中,f(5)并且*p都是指定临时对象的左值.这是表达类别规则存在的一个很好的例子; 通过遵循规则而没有棘手的解决方法,那么我们将尝试通过悬空指针写入的代码出错.
请注意,您也可以使用它f来查找临时对象的内存地址,例如std::cout << &f(5);
总之,您实际问的所有问题都错误地将表达式与对象混为一谈.因此,在这个意义上,它们不是问题.临时不是左值,因为对象不是表达式.
一个有效但相关的问题是:"为什么创建临时对象的表达式是一个右值(而不是左值?)"
正如上面所讨论的那样:将它作为左值会增加创建悬空指针或悬挂引用的风险; 如此3 = 5;,会增加指定程序员可能不想要的冗余操作的风险.
我再说一遍,表达类别是一个帮助程序员表达的设计决策; 与内存或存储位置无关.
最后,到抽象机器和as-if规则.C++是根据抽象机器定义的,其中临时对象也有存储和地址.我之前举了一个例子,说明如何打印临时对象的地址.
在AS-如果规则说,实际的可执行文件的编译器生成的输出只能匹配输出的抽象机会.可执行文件实际上不必以与抽象机器相同的方式工作,它只需产生相同的结果.
所以对于像这样的代码x = 5; ,即使临时的有价值对象5在抽象机器中有一个内存位置; 编译器不必在真实机器上分配物理存储.它只需要确保x最终5存储在其中并且有更简单的方法来执行此操作,而不涉及创建额外的存储.
在AS-如果规则适用于一切在节目中,即使我在这里的例子仅是指临时对象.非临时对象同样可以进行优化,例如int x; int y = 5; x = y; // other code that doesn't use y可以更改为int x = 5;.
这同样适用于没有会改变程序输出的副作用的类类型.例如std::string x = "foo"; std::cout << x;,std::cout << "foo";即使左值x表示具有存储在抽象机器中的对象,也可以优化.
lvalue代表定位器值,代表一个对象,它在内存中占据一些可识别的位置。
术语定位器值也用于此处:
C
C 编程语言遵循类似的分类法,只是赋值的作用不再重要:C 表达式被分类为“左值表达式”和其他(函数和非对象值),其中“左值”表示标识一个表达式的表达式。对象,一个“定位器值”[4]。
不是 an 的所有东西lvalue都是通过排除 an rvalue。每个表达式都是 anlavalue或rvalue。
最初lvalue术语在 C 中用于表示可以保留在赋值运算符左侧的值。然而,随着const关键工作这改变了。不是所有的lvalues都可以赋值。那些可以被称为modifiable lvalues.
而且文字和临时变量不是左值,但没有给出这个陈述的理由。
根据这个答案文字lvalues在某些情况下可以是。
rvalue因为它们的大小已知,并且很可能直接嵌入到给定硬件架构上的机器命令中。的内存位置是5什么?lvalues因为它们具有不可预测的大小,除了作为内存中的对象之外,没有其他方法可以表示它们。一个lvalue可以转换为一个rvalue。例如在以下说明中
int a =5;
int b = 3;
int c = a+b;
Run Code Online (Sandbox Code Playgroud)
运营商+需要两个rvalues。所以a和在求和之前b转换为rvalues。另一个转换示例:
int c = 6;
&c = 4; //ERROR: &c is an rvalue
Run Code Online (Sandbox Code Playgroud)
相反,您不能将 an 转换rvalue为 anlvalue。
但是,您可以lvalue从rvalue例如生成一个有效的:
int arr[] = {1, 2};
int* p = &arr[0];
*(p + 1) = 10; // OK: p + 1 is an rvalue, but *(p + 1) is an lvalue
Run Code Online (Sandbox Code Playgroud)
在 C++11 中,右值引用与移动构造函数和移动赋值运算符相关。
您可以在这篇清晰且解释清楚的帖子中找到更多详细信息。
| 归档时间: |
|
| 查看次数: |
1940 次 |
| 最近记录: |