Why lifetime of temporary doesn't extend till lifetime of enclosing object?


Why lifetime of temporary doesn't extend till lifetime of enclosing object?
I know that a temporary cannot be bound to a non-const reference, but it can be bound to const reference. That is,
A & x = A(); //error
const A & y = A(); //ok
I also know that in the second case (above), the lifetime of the temporary created out of A()
extends till the lifetime of const reference (i.e y
).
A()
y
But my question is:
Can the const reference which is bound to a temporary, be further bound to yet another const reference, extending the lifetime of the temporary till the lifetime of second object?
I tried this and it didn't work. I don't exactly understand this. I wrote this code:
struct A
A() std::cout << " A()" << std::endl;
~A() std::cout << "~A()" << std::endl;
;
struct B
const A & a;
B(const A & a) : a(a) std::cout << " B()" << std::endl;
~B() std::cout << "~B()" << std::endl;
;
int main()
A a;
B b(a);
std::cout << "-----" << std::endl;
B b((A())); //extra braces are needed!
Output (ideone):
A()
B()
~B()
~A()
-----
A()
B()
~A()
~B()
Difference in output? Why the temporary object A()
is destructed before the object b
in the second case? Does the Standard (C++03) talks about this behavior?
A()
b
@Luchian: Yes. Have you not heard of Most vexing parse?
– Nawaz
Aug 4 '11 at 6:50
I have now. Thanks! :)
– Luchian Grigore
Aug 4 '11 at 6:51
Note that your program does not contain any examples of lifetime extension. Passing a temporary by const reference does not extend its lifetime, the temporary is still destroyed at the end of the full-expression.
– fredoverflow
Aug 4 '11 at 9:05
7 Answers
7
The standard considers two circumstances under which the lifetime of a temporary is extended:
§12.2/4 There are two contexts in which temporaries are destroyed at a different point than the end of the fullexpression. The first context is when an expression appears as an initializer for a declarator defining an object. In that context, the temporary that holds the result of the expression shall persist until the object’s initialization is complete. [...]
§12.2/5 The second context is when a reference is bound to a temporary. [...]
None of those two allow you to extend the lifetime of the temporary by a later binding of the reference to another const reference. But ignore the standarese and think of what is going on:
Temporaries are created in the stack. Well, technically, the calling convention might mean that a returned value (temporary) that fits in the registers might not even be created in the stack, but bear with me. When you bind a constant reference to a temporary the compiler semantically creates a hidden named variable (that is why the copy constructor needs to be accessible, even if it is not called) and binds the reference to that variable. Whether the copy is actually made or elided is a detail: what we have is an unnamed local variable and a reference to it.
If the standard allowed your use case, then it would mean that the lifetime of the temporary would have to be extended all the way until the last reference to that variable. Now consider this simple extension of your example:
B* f()
B * bp = new B(A());
return b;
void test()
B* p = f();
delete p;
Now the problem is that the temporary (lets call it _T
) is bound in f()
, it behaves like a local variable there. The reference is bound inside *bp
. Now that object's lifetime extends beyond the function that created the temporary, but because _T
was not dynamically allocated that is impossible.
_T
f()
*bp
_T
You can try and reason the effort that would be required to extend the lifetime of the temporary in this example, and the answer is that it cannot be done without some form of GC.
+1. Excellent explanation.
– Nawaz
Aug 4 '11 at 8:38
@Nawaz: I usually create mental diagrams with the objects and what goes on, similar to the small images you can find here for NRVO. Being able to draw that helps understanding, and it also helps me in remembering.
– David Rodríguez - dribeas
Aug 4 '11 at 11:59
No, the extended lifetime is not further extended by passing the reference on.
In the second case, the temporary is bound to the parameter a, and destroyed at the end of the parameter's lifetime - the end of the constructor.
The standard explicitly says:
A temporary bound to a reference member in a constructor's ctor-initializer (12.6.2) persists until the constructor exits.
This quotation doesn't talk about further binding to yet another const reference which a member of the class. So I'm a bit skeptical.
– Nawaz
Aug 4 '11 at 6:37
The standard explicitly lists a number of places where the lifetime is extended. Your case is not mentioned, suggesting that it doesn't happen there.
– Bo Persson
Aug 4 '11 at 6:42
There is no "extended lifetime". Passing a temporary by const reference does not extend its lifetime, the temporary is still destroyed at the end of the full-expression.
– fredoverflow
Aug 4 '11 at 9:55
This isn't the applicable rule. In C++0x, the rule on temporaries passed as function arguments governs. I don't know whether C++03 has such a rule.
– Ben Voigt
Aug 7 '11 at 23:36
Your example doesn't perform nested lifetime extension
In the constructor
B(const A & a_) : a(a_) std::cout << " B()" << std::endl;
The a_
here (renamed for exposition) is not a temporary. Whether an expression is a temporary is a syntactic property of the expression, and an id-expression is never a temporary. So no lifetime extension occurs here.
a_
Here's a case where lifetime-extension would occur:
B() : a(A()) std::cout << " B()" << std::endl;
However, because the reference is initialized in a ctor-initializer, the lifetime is only extended until the end of the function. Per [class.temporary]p5:
A temporary bound to a reference member in a constructor's ctor-initializer (12.6.2) persists until the constructor exits.
In the call to the constructor
B b((A())); //extra braces are needed!
Here, we are binding a reference to a temporary. [class.temporary]p5 says:
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.
Therefore the A
temporary is destroyed at the end of the statement. This happens before the B
variable is destroyed at the end of the block, explaining your logging output.
A
B
Other cases do perform nested lifetime extension
Aggregate variable initialization
Aggregate initialization of a struct with a reference member can lifetime-extend:
struct X
const A &a;
;
X x = A() ;
In this case, the A
temporary is bound directly to a reference, and so the temporary is lifetime-extended to the lifetime of x.a
, which is the same as the lifetime of x
. (Warning: until recently, very few compilers got this right).
A
x.a
x
Aggregate temporary initialization
In C++11, you can use aggregate initialization to initialize a temporary, and thus get recursive lifetime extension:
struct A
A() std::cout << " A()" << std::endl;
~A() std::cout << "~A()" << std::endl;
;
struct B
const A &a;
~B() std::cout << "~B()" << std::endl;
;
int main()
const B &b = B A() ;
std::cout << "-----" << std::endl;
With trunk Clang or g++, this produces the following output:
A()
-----
~B()
~A()
Note that both the A
temporary and the B
temporary are lifetime-extended. Because the construction of the A
temporary completes first, it is destroyed last.
A
B
A
In std::initializer_list<T>
initialization
std::initializer_list<T>
C++11's std::initializer_list<T>
performs lifetime-extension as if by binding a reference to the underlying array. Therefore we can perform nested lifetime extension using std::initializer_list
. However, compiler bugs are common in this area:
std::initializer_list<T>
std::initializer_list
struct C
std::initializer_list<B> b;
~C() std::cout << "~C()" << std::endl;
;
int main()
const C &c = C A() , A() ;
std::cout << "-----" << std::endl;
Produces with Clang trunk:
A()
A()
-----
~C()
~B()
~B()
~A()
~A()
and with g++ trunk:
A()
A()
~A()
~A()
-----
~C()
~B()
~B()
These are both wrong; the correct output is:
A()
A()
-----
~C()
~B()
~A()
~B()
~A()
§12.2/5 says “The second context [when the lifetime of a temporary
is extended] is when a reference is bound to a temporary.” Taken
literally, this clearly says that the lifetime should be extended in
your case; your B::a
is certainly bound to a temporary. (A reference
binds to an object, and I don't see any other object it could possibly
be bound to.) This is very poor wording, however; I'm sure that what is
meant is “The second context is when a temporary is used to
initialize a reference,” and the extended lifetime corresponds to
that of the reference initiailized with the rvalue expression creating
the temporary, and not to that of any other references which may later
be bound to the object. As it stands, the wording requires something
that simply isn't implementable: consider:
B::a
void f(A const& a)
static A const& localA = a;
called with:
f(A());
Where should the compiler put A()
(given that it generally cannot see
the code of f()
, and doesn't know about the local static when
generating the call)?
A()
f()
I think, actually, that this is worth a DR.
I might add that there is text which strongly suggests that my
interpretation of the intent is correct. Imagine that you had a second
constructor for B
:
B
B::B() : a(A())
In this case, B::a
would be directly initialized with a temporary; the
lifetime of this temporary should be extended even by my interpretation.
However, the standard makes a specific exception for this case; such a
temporary only persists until the constructor exits (which again would
leave you with a dangling reference). This exception provides a very
strong indication that the authors of the standard didn't intend for
member references in a class to extend the lifetime of any temporaries
they are bound to; again, the motivation is implementability. Imagine
that instead of
B::a
B b((A()));
you'd written:
B* b = new B(A());
Where should the compiler put the temporary A()
so that it's lifetime
would be that of the dynamically allocated B
?
A()
B
+1. Awesome analysis of the Standard text.
– Nawaz
Aug 4 '11 at 8:44
I disagree that
B::a
is bound to a temporary. The expression it's bound to is formed by (implicit) dereference of a parameter. That's an l-value (albeit const
), not a temporary, in this context. The text of C++0x is also very clear about these cases: "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." and "A temporary bound to a reference in a new-initializer (5.3.4) persists until the completion of the full-expression containing the new-initializer."– Ben Voigt
Aug 7 '11 at 23:29
B::a
const
@Ben Voigt It's a question of terminology. A reference isn't bound to an expression. It's bound to an object. A reference is initialized by an expression; if that expression is an lvalue, it is bound to the object designated by that lvalue. If the lvalue is a reference which designates a temporary, then the reference is bound to that temporary (an object).
– James Kanze
Aug 8 '11 at 7:58
@James: All true. But regardless of the phrasing used by the standard, temporary-ness is a property of the expression, not the object. Unless you want to read "persists until" as "persists at least until". But then you'd lose deterministic destruction of temporaries, which IMO is even worse.
– Ben Voigt
Aug 8 '11 at 21:05
@Ben Voigt In the vocabulary of the standard, objects are temporary or not; expressions are rvalues or lvalues. In contexts where an object is needed (like initializing a reference), an rvalue expression will result in the creation of a temporary object. A reference is initialized with an expression (lvalue or rvalue), which results in it being bound to an object (temporary or not). A reference initialized with an rvalue expression is bound to a temporary; that reference, used in an expression, is an lvalue referring to a temporary object.
– James Kanze
Aug 9 '11 at 9:17
In your first run, the objects are destroyed in the order they were pushed on the stack -> that is push A, push B, pop B, pop A.
In the second run, A's lifetime ends with the construction of b. Therefore, it creates A, it creates B from A, A's lifetime finishes so it is destroyed, and then B is destroyed. Makes sense...
Not Really. When exactly is A's lifetime finishing ? After B's constructor? If its that, one more guy had the same answer but deleted his answer after some time.
– Arunmu
Aug 4 '11 at 6:25
This doesn't answer my question. I'm further binding const reference (to the temporary), to another const reference (member), yet the temporary gets destroyed before. I specifically want to know what is this not possible? (As for the record, from the output I can interpret the order of destructions of the objects; in fact anyone can explain that. The question is, why the objects gets destructed in that order?)
– Nawaz
Aug 4 '11 at 6:25
I don't know about standards, but can discuss some facts which I saw in few previous questions.
The 1st output is as is for obvious reasons that a
and b
are in the same scope. Also a
is destroyed after b
because it's constructed before b
.
a
b
a
b
b
I assume that you should be more interested in 2nd output. Before I start, we should note that following kind of object creations (stand alone temporaries):
A();
last only till the next ;
and not for the block surrounding it. Demo. In your 2nd case, when you do,
;
B b((A()));
thus A()
is destroyed as soon as the B()
object creation finishes. Since, const reference can be bind to temporary, this will not give compilation error. However it will surely result in logical error if you try to access B::a
, which is now bound to already out of scope variable.
A()
B()
B::a
§12.2/5 says
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.
Pretty cut and dried, really.
By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.
B b((A())); //extra braces are needed! - can you please explain this?
– Luchian Grigore
Aug 4 '11 at 6:48