Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extending temporary's lifetime, works with block-scoped aggregate, but not through `new`; why?

Note: This question was originally asked as a comment by Ryan Haining on this answer.


struct A { std::string const& ref; };

// (1)

A a { "hello world" };              // temporary's lifetime is extended to that of `a`
std::cout << a.ref << std::endl;    // safe

// (2)

A * ptr = new A { "hello world" };  // lifetime of temporary not extended?
std::cout << ptr->ref << std::endl; // UB: dangling reference


Question

  • Why is the lifetime of the temporary extended in (1), but not in (2)?
like image 466
Filip Roséen - refp Avatar asked May 31 '14 13:05

Filip Roséen - refp


1 Answers

LONG STORY, SHORT

The compiler cannot extend the lifetime of the temporary involved in new A { "temporary " }, because the A created, and the temporary, has different storage durations.

A refence to what the Standard says can be found at the end of this post. The Standard explicitly says that the lifetime will not be extended, but it doesn't go into detail to why this is.

This post will try explain the reason in a way that is understandable for a broader audience, not only to the average language-lawyer.


Introduction

In C++ there are several types of different storage durations an object can have, among them are automatic- and dynamic storage duration, explained briefly below:

Automatic storage duration

The storage for an object with automatic storage duration will persist until the block in which they are created exits.

  • Objects declared in block-scope has automatic storage duration (unless they are declared static or extern, but not register).

  • Temporaries are, by definition, declared at block-scope so they too have automatic storage duration.


Dynamic storage duration

The storage for an object with dynamic storage duration will persist until it is explicitly stated that it should be released; such storage is, in other words, not bound to any specific scope.

  • Objects created dynamically through operator new have, as hinted, dynamic storage duration.

    The storage will be persist until a matching call to operator delete has been made.




Aggregate initialization with automatic storage duration

As stated in the previous section, a temporary has automated storage duration.

If we construct an aggregate with automatic storage duration, this too will have storage bound to the current scope; meaning that the lifetime of the temporary can easily be extended to match that of the aggregate.

Note: We can imagine them living in the same "box", and at the end of the scope we discard this box, which is fine; neither the temporary, nor the aggregate, will outlive the lifetime of the box.


Our implementation (A)

struct A { std::string const& ref; };

void func () {
  A x { {"hello world"} };
}

Behind the scenes (A)

Since both x, and the temporary, have automatic storage duration, the compiler can implement the function as the following, semantically equivalent, snippet:

void  __func () {
  std::string __unnamed_temporary { "hello world" };
  A x { __unnamed_temporary };
}

Note: Both the temporary and the aggregate has their lifetime bound to the current scope, awesome!



Aggregate initialization with dynamic storage duration

Our implementation (B)

A* gunc () {
  A *    ptr = new A { { "hello world" } };
  return ptr;
}

int main () {
  A * p = gunc ();

  std::cout << p->ref << std::endl; // DANGER, WILL ROBINSON!

  delete p;
}

In the previous sections it has been stated that temporaries have automatic storage duration, which means that our temporary, bound to A::ref, will be constructed on storage that resides in the current scope.


Behind the scene (B)

The semantically equivalence of gunc can look as the below implementation:

A* gunc () {
  A __unnamed_temporary { "hello world " };

  A * ptr = new A { __unnamed_temporary }; // (1)

  return ptr;
}

You are thinking it too, aren't you?

No longer can we extend the lifetime of our temporary to match that of the A created with dynamic storage duration, at (1).

The problem is that automatic storage for __unnamed_temporary will disappear as soon as we return from gunc, effectively killing our temporary.

The dynamically created A will however still be alive, leaving us with a dangling reference in main.



Conclusion

The compiler is unable to extend the lifetime of any temporaries involved when creating an object through a new-initializer because the newed object, and the temporaries, will have different storage duration.



What does the Standard (n3797) say?

12.2p5 Temporary objects [class.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 in a new-initializer (5.3.4) persists until the completion of the full-expression containing the new-initializer.

    [ Note: This may introduce a dangling reference, and implementations are encouraged to issue a warning in such case. -- end note ]

like image 89
Filip Roséen - refp Avatar answered Oct 14 '22 23:10

Filip Roséen - refp