14

rvalue references: what exactly are "temporary" objects, what is their scope, and where are they stored?

Reading some articles, rvalues are always defined as "temporary" objects like Animal(), where Animal is a class, or some literal e.g. 10.

However, what is the formal definition of rvalues/"temporary" objects?

Is new Animal() also considered a "temporary" object? Or is it only values on the stack, like Animal() and literals stored in code?

Also, where are these "temporary" objects stored, what is their scope, and how long are rvalue references to these values valid?

Shuzheng
  • 11,288
  • 20
  • 88
  • 186
  • 2
    You want to read section 12.2 of the standard : http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4567.pdf (page 270). – Jesper Juhl Mar 26 '16 at 07:37

2 Answers2

18

Firstly it is important not to conflate the terms "rvalue" and "temporary object". They have very different meanings.


Temporary objects do not have a storage duration. Instead, they have lifetime rules that are specific to temporary objects. These can be found in section [class.temporary] of the C++ Standard; there is a summary on cppreference, which also includes a list of which expressions create temporary objects.

In practice I'd expect that a compiler would either optimize the object out, or store it in the same location as automatic objects are stored.

Note that "temporary object" only refers to objects of class type. The equivalent for built-in types are called values. (Not "temporary values"). In fact the term "values" includes both values of built-in type, and temporary objects.

A "value" is a completely separate idea to prvalue, xvalue, rvalue. The similarity in spelling is unfortunate.

Values don't have scope. Scope is a property of a name. In many cases the scope of a name coincides with the lifetime of the object or value it names, but not always.


The terms rvalue, lvalue etc. are value categories of an expression. These describe expressions, not values or objects.

Every expression has a value category. Also, every expression has a value, except expressions of void type. These are two different things. (The value of an expression has a non-reference type.)

An expression of value category rvalue may designate a temporary object, or a non-temporary object, or a value of built-in type.

The expressions which create a temporary object all have value category prvalue, however it is then possible to form expressions with category lvalue which designate that same temporary object. For example:

const std::string &v = std::string("hello");

In this case v is an lvalue, but it designates a temporary object. The lifetime of this temporary object matches the lifetime of v, as described in the earlier cppreference link.

Link to further reading about value categories


An rvalue reference is a reference that can only bind to an expression of value category rvalue. (This includes prvalue and xvalue). The word rvalue in its name refers to what it binds to, not its own value category.

All named references in fact have category lvalue. Once bound, there is no difference in behaviour between an rvalue reference and an lvalue reference.

std::string&& rref = std::string("hello");

rref has value category lvalue , and it designates a temporary object. This example is very similar to the previous one, except the temporary object is non-const this time.

Another example:

std::string s1("hello");
std::string&& rref = std::move(s1);
std::string& lref = s1;

In this case, rref is an lvalue, and it designates a non-temporary object. Further, lref and rref (and even s1!) are all indistinguishable from hereon in, except for specifically the result of decltype.

Community
  • 1
  • 1
M.M
  • 138,810
  • 21
  • 208
  • 365
  • Thanks for your answer. This is very good. However, how long is a rvalue reference valid? A normal reference is valid until the lvalue it refers to go out of scope? – Shuzheng Mar 26 '16 at 12:10
  • A reference is valid so long as the object (or value) it refers to has not ended its lifetime. – M.M Mar 26 '16 at 13:01
  • lvalues don't have scope - names have scope. – M.M Mar 26 '16 at 13:08
  • Are you sure that rvalue expressions can designate non-temporary objects? ("An expression of value category rvalue may designate a temporary object, or a non-temporary object, or a value of built-in type."). By "designate" you mean the result of the expression? And what exactly are non-temporary objects? I hope you will answer these three questions - thank you much so far. – Shuzheng Mar 26 '16 at 16:20
  • @NicolasLykkeIversen Prvalues of non-class, non-array types are not objects. For example, for `int const foo();`, the expression `foo()` has type `int` (not `const`-qualified! there's nothing to modify), also, `int const& x = foo();` will create a new object ("a temporary" [dcl.init.ref]p5.2.2.2) Prvalues of class type can have their address taken in a member function; prvalues of array type need to support pointer arithmetic. Enumerators are another example of prvalues that don't refer to objects. – dyp Mar 26 '16 at 16:31
  • 1
    @NicolasLykkeIversen Oops, I think I've misunderstood your question. Consider also `int x = 42;` then `std::move(x)` is an rvalue-expression referring to an object of automatic storage duration (not a temporary). – dyp Mar 26 '16 at 16:38
  • @NicolasLykkeIversen "non-temporary object" means an object that is not a temporary object. For example `s1` in my last example. The expression `std::move(s1)` is an rvalue that designates an object that is not a temporary object. – M.M Mar 27 '16 at 00:47
  • 1
    @Nik-Lz `S{5}` is a prvalue – M.M Sep 24 '18 at 01:55
  • 1
    @Nik-Lz see "Link to further reading about value categories" in my answer – M.M Sep 24 '18 at 02:22
  • 1
    @Nik-Lz prvalues represent a pre-materialization state – M.M Sep 24 '18 at 04:36
4

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.

3442
  • 8,248
  • 2
  • 19
  • 41
  • "scope" applies to *names*, not objects. Often the scope of a name coincides with the lifetime of the object it names, but not always. In your answer, most of the times you say "scope" you are describing "lifetime". – M.M Mar 26 '16 at 11:32
  • In your last paragraph, what rvalue references are you refering to? – M.M Mar 26 '16 at 11:34
  • @M.M: WRT rvalue references in the last paragraph: That was in response to a side question made by the OP. Will edit to use better wording as you suggested in your first comment. – 3442 Mar 26 '16 at 12:15
  • @M.M: Ah, now I understand what you meant by "what rvalur references". I missed a few words there, sorry. – 3442 Mar 26 '16 at 12:33
  • *"For rvalues, this is always the case, as you can't take a rvalue's address."* I don't think this is correct. rvalue is an expression category, it doesn't tell you anything about the object the expression is referring to. For example, `std::move(some_named_object)` is an rvalue-expression referring to an object whose address can be taken (syntactically). Furthermore, for class-type objects, `this` is available in member functions even on objects created as prvalues. (prvalues of fundamental type are treated differently, they are not assumed to be objects from the language's point of view.) – dyp Mar 26 '16 at 12:44
  • *"their lifetime sexplicitly"* You probably want to fix that typo. It sounds weird ;) – dyp Mar 26 '16 at 12:45
  • I'm not sure how relevant this is in this context, but C++ does distinguish between the "lifetime" of an allocation and the lifetime of an object stored within that allocation (for all types of storage duration). For example, you can end the lifetime of an object and, if its allocation is not freed (from the language's point of view!) you can create a new object within that allocation. The language under certain requirements then provides you with guarantees, such as being able to refer to the new object using a pointer that pointed to the old one. – dyp Mar 26 '16 at 12:49
  • @dyp: I've certainly have to admit why C people often call C++ "bloated" and "all-paradigm". This is just way too much paint to cover a giant misdesign. I mean, we're not getting anything useful by talking about xvalues, prvalues, etc... Anyway, I can criticize the language other day. For now, I'll just notice that when saying "rvalues", I'm talking in the C sense/C++98 sense and refer to the comments for details. The OP has not to deal with all of these. And having to remember all the special rules and nifty corners. **Edit**: Oh no, `sexplicitly` no, that was a way too bad typo.. – 3442 Mar 26 '16 at 12:50