Xvalues vs prvalues: what does identity property add
I'm sorry for the broadness of the question, it's just that all these details are tightly interconnected..
I've been trying to understand the difference between specifically two value categories - xvalues and prvalues, but still I'm confused.
The property of having identity (which makes xvalue different from prvalue) is discussed in the following question on SO:
Is it correct to say that xvalues have identity and are movable?
First of all, the accepted answer quotes §5/7 for the definition of xvalue; cppreference (http://en.cppreference.com/w/cpp/language/value_category) provides a much broader definition of xvalue - even if we only consider 2 latter cases from 5/7, according to cppreference (and to the compilers as I know as well) the first expression can be generic rvalue, not necessarily an xvalue. So which source is more correct here?
Anyway, the mental model I tried to develop for myself for the notion of 'identity' is that the expression that has it should be guaranteed to reside in the actual program's data memory.
Like for this reason string literals are lvalues, they're guaranteed to reside in memory for the entire program run, while number literals are prvalues and could eg hypothetically be stored in straight asm.
The same seems to apply to std::move
from prvalue literal, ie when calling fun(1)
we would get only the parameter lvalue in the callee frame, but when calling fun(std::move(1))
the xvalue 'kind' of glvalue must be kept in the caller frame.
However this mental model doesn't work at least with temporary objects, which, as I understand, should always be created in the actual memory (eg if a rvalue-ref-taking func is called like fun(MyClass())
with a prvalue argument). So I guess this mental model is wrong.
So what would be the correct way to think about the 'identity' property of xvalues? I've read that with identity I can compare addresses but if I could compare addresses of 2 MyClass().member
s (xvalue according to the cppreference), let's say by passing them by rvalue refs into some comparison function, then I don't understand why I can't do the same with 2 MyClass()
s (prvalue)?
One more source that's connected to this is the answer here: What are move semantics?
Note that even though std::move(a) is an rvalue, its evaluation does not create a temporary object. This conundrum forced the committee to introduce a third value category. Something that can be bound to an rvalue reference, even though it is not an rvalue in the traditional sense, is called an xvalue (eXpiring value).
But this seems to have nothing to do with 'can compare addresses' and a) I don't see how this is different from the 'traditional sense' of the rvalue; b) I don't understand why such a reason would require a new value category in the language (well, OK, this allows to provide dynamic typing for objects in OO sense, but xvalues don't only refer to objects).
I personally have another mental model which doesn't deal directly with identity and memory and whatnot.
prvalue
comes from "pure rvalue" while xvalue
comes from "expiring value" and is this information I use in my mental model:
Pure rvalue refers to an object that is a temporary in the "pure sense": an expression for which the compiler can tell with absolute certainty that its evaluation is an object that is a temporary that has just been created and that is immediately expiring (unless we intervene to prolong it's lifetime by reference binding). The object was created during the evaluation of the expression and it will die according to the rules of the "mother expression".
By contrast, an expiring value is a expression that evaluates to a reference to an object that is promised to expire soon. That is it gives you a promise that you can do whatever you want to this object because it will be destroyed next anyway. But you don't know when this object was created, or when it is supposed to be destroyed. You just know that you "intercepted" it as it is just about to die.
In practice:
struct X;
auto foo() -> X;
X x = foo();
^~~~~
in this example evaluating foo()
will result in a prvalue
. Just by looking at this expression you know that this object was created as part of the return of foo
and will be destroyed at the end of this full expression. Because you know all of these things you can prologue it's lifetime:
const X& rx = foo();
now the object returned by foo has it's lifetime prolongued to the lifetime of rx
auto bar() -> X&&
X x = bar();
^~~~
In this example evaluating bar()
will result in a xvalue
. bar
promises you that is giving you an object that is about to expire, but you don't know when this object was created. It can be created way before the call to bar
(as a temporary or not) and then bar
gives you an rvalue reference
to it. The advantage is you know you can do whatever you want with it because it won't be used afterwords (eg you can move from it). But you don't know when this object is supposed to be destroyed. As such you cannot extend it's lifetime - because you don't know what its original lifetime is in the first place:
const X& rx = bar();
this won't extend the lifetime.
When calling a func(T&& t)
the caller is saying "there's at here" and also "I don't care what you do to it". C++ does not specify the nature of "here".
On a platform where reference parameters are implemented as addresses, this means there must be an object present somewhere. On that platform identity == address. However this is not a requirement of the language, but of the platform calling convention.
A platform could implement references simply by arranging for the objects to be enregistered in a particular manner in both the caller and callee. Here an identity could be "register edi".
链接地址: http://www.djcxy.com/p/73000.html上一篇: 什么是左值?