开发者

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

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()  { s开发者_开发知识库td::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?


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.


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.


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


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


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


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.


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

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜