9
struct A {
    A(int) : i(new int(783)) {
        std::cout << "a ctor" << std::endl;
    }

    A(const A& other) : i(new int(*(other.i))) {
        std::cout << "a copy ctor" << std::endl;
    }

    ~A() {
        std::cout << "a dtor" << std::endl;
        delete i;
    }

    void get() {
        std::cout << *i << std::endl;
    }

private:
    int* i;
};

const A& foo() {
    return A(32);
}

const A& foo_2() {
    return 6;
}

int main()
{
    A a = foo();
    a.get();
}

I know, returning references to local values is bad. But, on the other hand, const reference should extend a temporary object lifetime.

This code produce an UB output. So no life extention.

Why? I mean can someone explain whats happening step by step?

Where is fault in my reasoning chain?

foo():

  1. A(32) - ctor

  2. return A(32) - a const reference to local object is created and is returned

  3. A a = foo(); - a is initialized by foo() returned value, returned value goes out of scope(out of expression) and is destroyed, but a is already initialized;

(But actually destructor is called before copy constructor)

foo_2():

  1. return 6 - temp object of type A is created implicitly,a const reference to this object is created(extending its life) and is returned

  2. A a = foo(); - a is initialized by foo() returned value, returned value goes out of scope(out of expression) and is destroyed, but a is already initialized;

(But actually destructor is called before copy constructor)

Wug
  • 12,956
  • 4
  • 34
  • 54
Alexander
  • 779
  • 8
  • 17
  • 1
    "const reference should extend a temporary object lifetime" <- erm, no when it comes to object-lifetime non-const and const references are equal and do *not* extend it. – Giel Jul 31 '12 at 19:36
  • 2
    I think this is what Alexander is talking about in terms of extending the lifetime: http://herbsutter.com/2008/01/01/gotw-88-a-candidate-for-the-most-important-const/ – vmpstr Jul 31 '12 at 19:44
  • 3
    @Giel: No. Const reference **can** extend lifetime of temporary object. Const- and non-const references are quite different when it comes to working with temporaries. In this case it just works differently from what the OP seems to expect. – AnT stands with Russia Jul 31 '12 at 19:45
  • @AndreyT: right, the link vmpstr gave explains it nicely. In OP's case however the *reference* is the temporary that gets returned, not the referred-to object. So as expected it doesn't extend the temporary's life-time beyond the scope that spawned it. The latter I believe is still correct in all cases: local objects (temporary or as a local variable) do *not* live beyond there scope. – Giel Jul 31 '12 at 20:09
  • No, they don't live beyond their scope, but often their dead bodies are still there and "seem" to be functioning & operational, which can be confusing because returning the reference "seems to work fine" in some circumstances but not others. Though it's a little extra work, to prevent this confusion (and confusion from using stale pointers), I like to NULL-out member pointers in the destructor after deleting them, and in some cases setting member variables to safe invalid states, so if somebody tries using a deleted object, it's immediately obvious. – phonetagger Jul 31 '12 at 21:40

2 Answers2

12

Rules of temporary lifetime extension for each specific context are explicitly spelled out in the language specification. And it says that

12.2 Temporary objects

5 The second context is when a reference is bound to a temporary. [...] A temporary bound to the returned value in a function return statement (6.6.3) persists until the function exits. [...]

Your temporary object is destroyed at the moment of function exit. That happens before the initialization of the recipient object begins.

You seem to assume that your temporary should somehow live longer than that. Apparently you are trying to apply the rule that says that the temporary should survive until the end of the full expression. But that rule does not apply to temporaries created inside functions. Such temporaries' lifetimes are governed by their own, dedicated rules.

Both your foo and your foo_2 produce undefined behavior, if someone attempts to use the returned reference.

Community
  • 1
  • 1
AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • But if "a temporary bound to the returned value in a function return statement (6.6.3) persists until the function exits" shoudnt temporary value be destroyed before initialization in A foo_3(){return A(54);} ? – Alexander Jul 31 '12 at 19:53
  • 1
    @Alexander - No, `A foo_3()` returns a copy of the value. The copied value is not destroyed at the end of the function. When you return a reference, the reference is also still there - it just doesn't refer to anything anymore. – Bo Persson Jul 31 '12 at 20:08
  • I see.. But then copy ctor should be called 2 times in expression A a = foo_3(); 1st time when returning value is copied from temporary local and 2nd time when initializing A a. But its called only once. Or its just an optimization? – Alexander Jul 31 '12 at 20:13
  • 1
    @Alexander: When you do `A foo_3(){return A(54);}`, you have *two* different conceptual temporaries. The first one is the one you explicitly created - `A(54)`. The second one is a special internal "transitional" temporary that holds the result after function exits. The `A(54)` temporary is copied to the "transitional" temporary and `A(54)` gets destroyed. The "transitional" temporary lives longer to be used as the initializer for the recipient object. – AnT stands with Russia Jul 31 '12 at 20:38
  • For `A a = foo();`, if the returned object is destroyed, than what is assigned to a? where does it refer to? – Reeonce Zeng Jan 11 '17 at 14:28
5

You are misinterpeting "until function exit". If you really want to use a const reference to extend the life of an object beyond foo, use

A foo() {
    return A(32);
}
int main() {
    const A& a = foo();
}

You must return from foo by value, and then use a const reference to reference the return value, if you wish to extend things in the way you expect.

As @AndreyT has said, the object is destroyed in the function that has the const &. You want your object to survive beyond foo, and hence you should not have const & (or &) anywhere in foo or in the return type of foo. The first mention of const & should be in main, as that is the function that should keep the object alive.

You might think this return-by-value code is slow as there appear to be copies of A made in the return, but this is incorrect. In most cases, the compiler can construct A only once, in its final location (i.e. on the stack of the calling function), and then set up the relevant reference.

Aaron McDaid
  • 26,501
  • 9
  • 66
  • 88