There are two different things to concern about. First of all, there's the language's point of view. Language specifications, such as the C++ standard(s), don't talk about things such as CPU registers, cache coherence, stacks (in the assembly sense), etc... Then, there's a real machine's point of view. Instruction set architectures (ISAs), such as the one(s) defined by Intel manuals, do concern about this stuff. This is, of course, because of portability and abstraction. There's no good reason for C++ to depend on x86-specific details, but a lot of bad ones. I mean, imagine if HelloWorld.cpp
would only compile for your specific Core i7 model for no good reason at all! At the same time, you need CPU specific stuff sometimes. For instance, how would you issue a CLI instruction in a portable way? We have different languages because we need to solve different tasks, and we have different ISAs because we need different means to solve them. There's a good reason explaining why your smartphone doesn't use an Intel CPU, or why the Linux kernel is written in C and not, ahem... Brainfuck.
Now, from the language's point of view, a "rvalue" is a temporary value whose lifetime ends at the expression it is evaluated in.
In practice, rvalues are implemented the same way as automatic variables, that is, by storing their value on the stack, or a register if the compiler sees it fit. In the case of an automatic variable, the compiler can't store it in a register if its address is taken somewhere in the program, because registers have no "address". However, if its address is never taken, and no volatile
stuff is involved, then the compiler's optimizer can place that variable into a register for optimization's sake. For rvalues, this is always the case, as you can't take a rvalue's address. From the language's point of view, they don't have one (Note: I'm using oldish C terminology here; see the comments for details, as there are way too many C++11 pitfalls to annotate here). This is necessary for some things to work properly. For instance, cdecl
requires that small values be returned in the EAX
register. Thus, all function calls must evaluate into a rvalue (consider references as pointers for simplicity's sake), because you can't take a register's address, as they don't have one!
There's also the concept of "lifetime". From the language's perspective, once some object's lifetime "ends", it ceases to be, period. When does it "begins" and "ends" depends on the object's allocation means:
- For objects with dynamic storage, their lifetime sexplicitly start by means of
new
expressions and explicitly end by means of delete
statements. This mechanism allows them to survive their original scope (e.g: return new int;
).
- For objects with automatic storage, their lifetimes start when their scope is reached in the program flow, and end when their scope is exited.
- For objects with static storage, their lifetimes start before
main()
is called and end once main()
exits.
- For objects with thread-local storage, their lifetimes start when their respective thread starts, and end when their respective thread exits.
Construction and destruction are respectively involved in an object's lifetime "start" and "end".
From a real machine's point of view, bits are just bits, period. There are no "objects" but bits and bytes in memory cells and CPU registers. For things like an int
, that is, a POD type, "ending its lifetime" translates into doing nothing at all. For non-trivially destructible non-POD types, a destructor must be called at the right moment. However, the memory/register that once contained the "object" is still there. It just happens that it can now be reused by something else.
Is new Animal() also considered a "temporary" object? Or is it only values on the stack, like Animal() and literals stored in code?
new Animal()
allocates memory in the heap for an Animal
, constructs it, and the whole expression evaluates into a Animal*
. Such an expression is an rvalue itself, as you can't say something like &(new Animal())
. However, the expression evaluates into a pointer, no? Such a pointer points to an lvalue, as you can say things such as &(*(new Animal()))
(will leak, though). I mean, if there's a pointer containing its address, it has an address, no?
Also, where are these "temporary" objects stored, what is their scope, and how long are rvalue references to these values valid?
As explained above, a "temporary object"'s scope is that of the expression that encloses it. For example, in the expression a(b * c)
(assuming a
is a function taking a rvalue reference as its single argument), b * c
is an rvalue whose scope ends after the expression enclosing it, that is, A(...)
, is evaluated. After that, all remaining rvalue references to it that the function a
may have somehow created out of its parameter are dangling and will cause your program to do funny things. In order words, as long as you don't abuse std::move
or do other voodoo with rvalues, rvalue references are valid in the circumstances that you'ld expect them to be.