7

In 12.2 of C++11 standard:

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:

  1. A temporary bound to a reference member in a constructor’s ctor-initializer (12.6.2) persists until the constructor exits.

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

  3. The lifetime of a temporary bound to the returned value in a function return statement (6.6.3) is not extended; the temporary is destroyed at the end of the full-expression in the return statement.

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

And there is an example of the last case in the standard:

struct S {
  int mi; 
  const std::pair<int,int>& mp;
}; 
S a { 1,{2,3} };  // No problem.
S* p = new S{ 1, {2,3} };  // Creates dangling reference

To me, 2. and 3. make sense and easy to agree. But what's the reason bebind 1. and 4.? The example looks just evil to me.

Community
  • 1
  • 1
updogliu
  • 6,066
  • 7
  • 37
  • 50
  • @Mehrdad: the reference is in the struct S. What I find weird personnally is that the standards says `S a{ 1,{2,3} };` causes no problem. It's not even compiling on my computer (Mac OS 10.8 with Xcode 5) – Kevin MOLCARD Feb 20 '14 at 21:54
  • @KevinMOLCARD: Oh whoops I totally missed that reference in the struct, my bad... deleted my comment. – user541686 Feb 20 '14 at 21:56
  • There must be another duplicate somewhere, but the best I can find is http://stackoverflow.com/questions/5719636/lifetime-of-temporary-bound-to-aggregate-initialized-struct-member … not a very good answer. – Potatoswatter Feb 20 '14 at 23:04

3 Answers3

5

As with many things in C and C++, I think this boils down to what can be reasonably (and efficiently) implemented.

Temporaries are generally allocated on the stack, and code to call their constructors and destructors are emitted into the function itself. So if we expand your first example into what the compiler is actually doing, it would look something like:

  struct S {
    int mi;
    const std::pair<int,int>& mp;
  };

  // Case 1:
  std::pair<int,int> tmp{ 2, 3 };
  S a { 1, tmp };

The compiler can easily extend the life of the tmp temporary long enough to keep "S" valid because we know that "S" will be destroyed before the end of the function.

But this doesn't work in the "new S" case:

  struct S {
    int mi;
    const std::pair<int,int>& mp;
  };

  // Case 2:
  std::pair<int,int> tmp{ 2, 3 };
  // Whoops, this heap object will outlive the stack-allocated
  // temporary!
  S* p = new S{ 1, tmp };

To avoid the dangling reference, we would need to allocate the temporary on the heap instead of the stack, something like:

   // Case 2a -- compiler tries to be clever?
   // Note that the compiler won't actually do this.
   std::pair<int,int> tmp = new std::pair<int,int>{ 2, 3 };
   S* p = new S{ 1, tmp };

But then a corresponding delete p would need to free this heap memory! This is quite contrary to the behavior of references, and would break anything that uses normal reference semantics:

  // No way to implement this that satisfies case 2a but doesn't
  // break normal reference semantics.
  delete p;

So the answer to your question is: the rules are defined that way because it sort of the only practical solution given C++'s semantics around the stack, heap, and object lifetimes.

WARNING: @Potatoswatter notes below that this doesn't seem to be implemented consistently across C++ compilers, and therefore is non-portable at best for now. See his example for how Clang doesn't do what the standard seems to mandate here. He also says that the situation "may be more dire than that" -- I don't know exactly what this means, but it appears that in practice this case in C++ has some uncertainty surrounding it.

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
Josh Haberman
  • 4,170
  • 1
  • 22
  • 43
  • 1
    It looks like this answer will be selected… it's only responsible to note that lifetime extension by binding to a nonstatic member reference is not portable. I think the situation is more dire than that, but I don't have evidence right now. – Potatoswatter Feb 21 '14 at 00:29
  • @Potatoswattr: thanks for the info, I updated my answer to include a warning about this. – Josh Haberman Feb 21 '14 at 04:46
  • I'm a dude. "More dire than that" means that it's not just unportable, but the standard doesn't say what the committee meant it to say, so any notion of official support is erroneous, and will only last until the standard is changed to remove it. – Potatoswatter Feb 21 '14 at 07:51
  • @Potatoswatter: that's a little unfortunate, it seems useful and easy enough to implement. Maybe a little too tricky/subtle to rely on this behavior in real programs? – Josh Haberman Feb 21 '14 at 16:17
  • I'm not sure about the "easy to implement;" I haven't put on my compiler-crashing hardhat yet but I don't think the solution is tenable. For example, a temporary bound into a return value object whose lifetime is extended by the caller would need a lifetime extension. – Potatoswatter Feb 22 '14 at 01:19
2

The main thrust is that reference extension only occurs when the lifetime can be easily and deterministically determined, and this fact can be deduced as possible on the line of code where the temporary is created.

When you call a function, it is extended to the end of the current line. That is long enough, and easy to determine.

When you create an automatic storage reference "on the stack", the scope of that automatic storage reference can be deterministically determined. The temporary can be cleaned up at that point. (Basically, create an anonymous automatic storage variable to store the temporary)

In a new expression, the point of destruction cannot be statically determined at the point of creation. It is whenever the delete occurs. If we wanted the delete to (sometimes) destroy the temporary, then our reference "binary" implementation would have to be more complicated than a pointer, instead of less or equal. It would sometimes own the referred to data, and sometimes not. So that is a pointer, plus a bool. And in C++ you don't pay for what you don't use.

The same holds in a constructor, because you cannot know if the constructor was in a new or a stack allocation. So any lifetime extension cannot be statically understood at the line in question.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
0

How long do you want the temporary object to last? It has to be allocated somewhere.

It can't be on the heap because it would leak; there is no applicable automatic memory management. It can't be static because there can be more than one. It must be on the stack. Then it either lasts until the end of the expression or the end of the function.

Other temporaries in the expression, perhaps bound to function call parameters, are destroyed at the end of the expression, and persisting until the end of the function or "{}" scope would be an exception to the general rules. So by deduction and extrapolation of the other cases, the full-expression is the most reasonable lifetime.

I'm not sure why you say this is no problem:

S a { 1,{2,3} };  // No problem.

The dangling reference is the same whether or not you use new.

Instrumenting your program and running it in Clang produces these results:

#include <iostream>

struct noisy {
    int n;
    ~noisy() { std::cout << "destroy " << n << "\n"; }
};

struct s {
    noisy const & r;
};

int main() {
    std::cout << "create 1 on stack\n";
    s a {noisy{ 1 }};  // Temporary created and destroyed.

    std::cout << "create 2 on heap\n";
    s* p = new s{noisy{ 2 }};  // Creates dangling reference
}

 

create 1 on stack
destroy 1
create 2 on heap
destroy 2

The object bound to the class member reference does not have an extended lifetime.

Actually I'm sure this is the subject of a known defect in the standard, but I don't have time to delve in right now…

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • "The dangling reference is the same whether or not you use new" -- I don't believe this is true, @updogliu specifically quoted language from the standard that the lifetime of the temporary is extended in this case. – Josh Haberman Feb 20 '14 at 22:26
  • @JoshHaberman I see four cases and none of them apply here. Care to clarify? – Potatoswatter Feb 20 '14 at 22:27
  • It is not one of the four cases. The rule quoted says that a temporary bound to a reference has its lifetime extended to the lifetime of the reference *unless* one of these cases applies. – Josh Haberman Feb 20 '14 at 22:30
  • "The object bound to the class member reference does not have an extended lifetime." That is surprising. In other cases binding a const ref does indeed extend the lifetime as one would expect from the above quote from the standard: https://gist.github.com/anonymous/9125173 – Josh Haberman Feb 20 '14 at 23:04
  • 1
    @Potatoswatter What version of clang? I'm getting [different results](http://coliru.stacked-crooked.com/a/ad229dcbe70b6063) from both clang and gcc. And to me it looks like the 4 cases are *exceptions* to the lifetime extension rule, and since none apply to the first example, the lifetime of that temporary does get extended. Or did I get it wrong somehow? – Praetorian Feb 20 '14 at 23:07
  • @Praetorian Indeed, I purposely cherry-picked the data for this answer. I'm sure it's been covered elsewhere on SO but I can't find anything, or an official DR. I don't have time for this right now. Perhaps this is just a Clang bug, but in any case using a nonstatic member reference to extend lifetime is unportable. – Potatoswatter Feb 21 '14 at 00:26