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

7

36

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).

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?

Fiscus answered 4/8, 2011 at 5:45 Comment(3)
B b((A())); //extra braces are needed! - can you please explain this?Shred
@Luchian: Yes. Have you not heard of Most vexing parse?Fiscus
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.Waxwing
T
25

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.

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.

Townshend answered 4/8, 2011 at 7:41 Comment(1)
@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.Boccioni
W
7

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.

Wastepaper answered 4/8, 2011 at 6:27 Comment(4)
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.Fiscus
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.Wastepaper
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.Waxwing
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.Lemley
M
5

§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:

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)?

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() : 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 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?

Monody answered 4/8, 2011 at 8:25 Comment(4)
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."Lemley
@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).Monody
@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.Lemley
@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.Monody
R
4

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.

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.

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).

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.

In std::initializer_list<T> initialization

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:

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()
Rehash answered 27/6, 2013 at 23:4 Comment(0)
S
1

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...

Shred answered 4/8, 2011 at 6:20 Comment(2)
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.Havoc
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?)Fiscus
A
1

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.

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.

Amphisbaena answered 4/8, 2011 at 6:34 Comment(0)
L
-1

§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.

Lemley answered 7/8, 2011 at 23:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.