C++: Life Span of Temporary Arguments

C++: Life span of temporary arguments?

Temporary objects are destroyed at the end of the full expression they're part of.

A full expression is an expression that isn't a sub-expression of some other expression. Usually this means it ends at the ; (or ) for if, while, switch etc.) denoting the end of the statement. In your example, it's the end of the function call.

Note that you can extend the lifetime of temporaries by binding them to a const reference. Doing so extends their lifetime to the reference's lifetime:

MyClass getMyClass();

{
const MyClass& r = getMyClass(); // full expression ends here
...
} // object returned by getMyClass() is destroyed here

If you don't plan to change the returned object, then this is a nice trick to save a copy constructor call (compared to MyClass obj = getMyClass();), in case return value optimization was not being applied. Unfortunately it isn't very well known. (I suppose C++11's move semantics will render it less useful, though.)

Guaranteed lifetime of temporary in C++?

The destructor for that sort of temporaries is called at the end of the full-expression. That's the most outer expression which is not part of any other expression. That is in your case after the function returns and the value is evaluated. So, it will work all nice.

It's in fact what makes expression templates work: They can keep hold references to that sort of temporaries in an expression like

e = a + b * c / d

Because every temporary will last until the expression

x = y

Is evaluated completely. It's quite concisely described in 12.2 Temporary objects in the Standard.

Why does extending the lifespan of a temporary object not result in the extension of the intermediate temporaries?

The lifetime extension rules are designed to:

  • prevent temporary objects from outliving the scope in which they are created;
  • allow the compiler to statically determine when lifetime extension occurs and when the extended lifetime ends.

Were this not so, there would be a runtime cost to lifetime extension, where every initialization of a pointer or reference would have to increment a reference count associated with the temporary object. If that's what you want, use std::shared_ptr.

In your example, the X::X(Y&&) constructor could be defined in another translation unit, so the compiler may not even be able to tell at translation time that it stores a reference to the temporary passed in. Programs are not supposed to behave differently depending on whether the function is defined in this translation unit or in another. Even if the compiler can see the definition of X::X, in principle the initializer for X::y could be an arbitrarily complex expression that may or may not actually result in an xvalue referring to the same object as the parameter y. It is not the compiler's job to attempt to decide potentially undecidable decision problems, even in special cases that are obvious to humans.

Temporary object argument lifetime in a function

Pre-C++17, sp is move-constructed from the temporary if the move is not elided to begin with. In either case, sp is the sole owner of the resource, so the use count is rightly reported as 1. This is overload 10) in this reference.

While the temporary still exists, if not elided, it is in a moved-from state and no longer holds any resource, so it doesn't contribute to the resource's use count.

Since C++17, no temporary is created thanks to guaranteed copy/move elision, and sp is constructed in place.


Exact wording from said reference:

10) Move-constructs a shared_ptr from r. After the construction, *this contains a copy of the previous state of r, r is empty and its stored pointer is null. [...]

In our case, r refers to the temporary and *this to sp.

The lifetime of a temporary to which several references are bound in C++

This is a defect in that section that I reported as http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1299 .

The proposed resolution is to add a term "temporary expressions". Life-time extension only happens for objects referred to by temporary expressions.

Here's my original report which I privately emailed. I think it makes clear what it's about

In the model of the Standard, there appears to be a distinction about
temporary objects, and temporary expressions.

Temporary objects are created by certain operations operations, like
functional casts to class types. Temporary objects have a limited
specified lifetime.

Temporary expressions are expressions that are so attributed because
they are used to track whether or not an expression refers to a
temporary object for the purpose of determining whether or not the
lifetime of their referent is lengthened when bound by a reference.
Temporary expressions are compile time entities.

Several paragraphs refer to "temporaries", but do not explicitly
specify whether they refer to temporary objects referred to by
arbitrary expressions, or whether they refer only to temporary
expressions. The paragraphs about RVO (paragraph 12.8p31) use
"temporary" in the sense of temporary objects (they say such things
like "temporary class object that has not been bound to a reference").
The paragraphs about lifetime lengthening (sub-clause 12.2) refer to
both kinds of temporaries. For example, in the following, "*this" is
not regarded as a temporary, even though it refers to a temporary

struct A { A() { } A &f() { return *this; } void g() { } };

// call of g() is valid: lifetime did not end prematurely
// at the return statement
int main () { A().f().g(); }

As another example, core issue 462 handles about temporary expressions
(making a comma operator expression a temporary, if the left operand
was one). This appears to be very similar to the notion of "lvalue
bitfields". Lvalues that track along that they refer to bitfields at
translation time, so that reads from them can act accordingly and that
certain reference binding scenarios can emit diagnostics.

C++ temporary variable lifespan shortened by binding to a reference member?

You're using the wrong bullet between the two.

This one:

A temporary bound to a reference member in a constructor's ctor-initializer ([class.base.init]) persists until the constructor exits.

does not apply here. We don't have a temporary bound to a reference member in a ctor-initializer. That situation would be more like:

struct B
{
B()
: memberRef(A(2)) // <==
{ }

~B();

const A& memberRef;
};

Our situation is exactly this one:

A temporary bound to a reference parameter in a function call ([expr.call]) persists until the completion of the full-expression containing the call.

We have a temporary (A(4)) bound to a reference parameter in a constructor (a constructor call is still a function call, the parameter we're binding to is the a in B(const A& a)), so the temporary persists until the completion of the full-expression.


In other words, there are no dangling references in what you've shown. All of these rules around binding temporaries to references are about lifetime extension. None of them shorten lifetimes.

What is the lifetime of a default argument temporary bound to a reference parameter?

The standard handles this in a special case in §12.2 [class.temporary]:

p4 There are two contexts in which temporaries are destroyed at a different point than the end of the full-expression. [...]

p5 The second context is when a reference is bound to a temporary. The temporary to which the reference is bound or the temporary that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference except:

  • A temporary bound to a reference parameter in a function call (5.2.2) persists until the completion of the full-expression containing the call.

The standard also has a handy note on full-expressions and the evaluation of their subexpressions with regards to default parameters in §1.9 [intro.execution] p11:

[ Note: The evaluation of a full-expression can include the evaluation of subexpressions that are not lexically part of the full-expression. For example, subexpressions involved in evaluating default arguments (8.3.6) are considered to be created in the expression that calls the function, not the expression that defines the default argument. —end note ]

C++ temporary objects lifetime in a function call

Temporaries live until the end of the full-expression in which they were created (with some life time extension exceptions), see [class.temporary]/4.

In your case the temporary of interest of type std::unique_ptr<A> is created by makeA() and the full-expression this is a subexpression of is f(*makeA());, so the temporary's life time will end at that semicolon.

The object that the unique_ptr manages is also destroyed only when the unique_ptr itself is destroyed (that is the purpose of the smart pointer).

For exceptions to that rule, see the following paragraph of the standard.

Temporary lifetime extension

What does until the function exits mean? Does it mean untill it finished executing?

Yes.

Why do I get a 5 output. Does a temporary object still exist on line #2?

Dereferencing a reference which is not bound to a living object is undefined behavior, so you may get 5 as well as 42 as well as anything else (including a crash). You simply cannot have any expectation on a program that has undefined behavior.

How can I interpret the standard quote to figure out how this example works?

Pretty much like you did already.The temporary gets bound to the function parameter fooRef, which gets destroyed when returning from the function. Since that temporary is bound to the returned value, that object ceases to exist when the function returns. Later on, you are dereferencing a dangling reference, which gives you UB.



Related Topics



Leave a reply



Submit