How Is Its Lifetime of a Return Value Extended to the Scope of the Calling Function When It Is Bound to a Const Reference in the Calling Function

How is its lifetime of a return value extended to the scope of the calling function when it is bound to a const reference in the calling function?

Normally a temporary object (such as one returned by a function call) has a lifetime that extends to the end of the "enclosing expression". However, a temporary bound to a reference generally has it's lifetime 'promoted' to the lifetime of the reference (which may or may not be the lifetime of the calling function), but there are a couple exceptions. This is covered by the standard in 12.2/5 "Temporary objects":

The temporary to which the reference is bound or the temporary that is the complete object to a subobject of which the temporary is bound persists for the lifetime of the reference except as specified below. A temporary bound to a reference member in a constructor’s ctor-initializer (12.6.2) persists until the constructor exits. 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.

See the following for more information:

  • C++ constant reference lifetime (container adaptor)
  • GotW #88: A Candidate For the "Most Important const"

An example that might help visualize what's going on:

#include <iostream>
#include <string>

class foo {
public:
foo( std::string const& n) : name(n) {
std::cout << "foo ctor - " << name + " created\n";
};
foo( foo const& other) : name( other.name + " copy") {
std::cout << "foo copy ctor - " << name + " created\n";
};

~foo() {
std::cout << name + " destroyed\n";
};

std::string getname() const { return name; };
foo getcopy() const { return foo( *this); };

private:
std::string name;
};

std::ostream& operator<<( std::ostream& strm, foo const& f) {
strm << f.getname();
return strm;
}

int main()
{
foo x( "x");

std::cout << x.getcopy() << std::endl;

std::cout << "note that the temp has already been destroyed\n\n\n";

foo const& ref( x.getcopy());

std::cout << ref << std::endl;

std::cout << "the temp won't be deleted until after this...\n\n";
std::cout << "note that the temp has *not* been destroyed yet...\n\n";
}

Which displays:

foo ctor - x created
foo copy ctor - x copy created
x copy
x copy destroyed
note that the temp has already been destroyed

foo copy ctor - x copy created
x copy
the temp won't be deleted until after this...

note that the temp has *not* been destroyed yet...

x copy destroyed
x destroyed

Why do const references extend the lifetime of rvalues?

It was proposed in 1993. Its purpose was to eliminate the inconsistent handling of temporaries when bound to references.

Back then, there was no such thing as RVO (return value optimization), so simply banning the binding of a temporary to a reference would have been a performance hit.

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/1993/N0345.pdf

Returning const reference to temporary behaves differently than local const reference?

Given const Val &foo = test(Val(5));, the temporary Val(5) will be destroyed after the full expression immediately, its lifetime won't be extended to the lifteime of the reference foo. It's not bound to foo directly, but bound to the reference parameter of test.

In reference initialization,

(emphasis mine)

Whenever a reference is bound to a temporary or to a subobject
thereof, the lifetime of the temporary is extended to match the
lifetime of the reference, with the following exceptions:

  • a temporary bound to a reference parameter in a function call exists until the end of the full expression containing that function call: if
    the function returns a reference, which outlives the full expression,
    it becomes a dangling reference.

In general, the lifetime of a temporary cannot be further extended by
"passing it on": a second reference, initialized from the reference to
which the temporary was bound, does not affect its lifetime.

Why doesn't a const reference extend the life of a temporary object passed via a function?

It's by design. In a nutshell, only the named reference to which the temporary is bound directly will extend its lifetime.

[class.temporary]

5 There are three contexts in which temporaries are destroyed at a
different point than the end of the full-expression. [...]

6 The third context is when a reference is bound to a 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 object bound to a reference parameter in a function call persists until the completion of the full-expression containing
    the call.
  • The lifetime of a temporary bound to the returned value in a function return statement is not extended; the temporary is destroyed
    at the end of the full-expression in the return statement.
  • [...]

You didn't bind directly to ref2, and you even pass it via a return statement. The standard explicitly says it won't extend the lifetime. In part to make certain optimizations possible. But ultimately, because keeping track of which temporary should be extended when a reference is passed in and out of functions is intractable in general.

Since compilers may optimize aggressively on the assumption that your program exhibits no undefined behavior, you see a possible manifestation of that. Accessing a value outside its lifetime is undefined, this is what return ref2; does, and since the behavior is undefined, simply returning zero is a valid behavior to exhibit. No contract is broken by the compiler.

What exactly happens when returning const reference to a local object?

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.

why use a const non-reference when const reference lifetime is the length of the current scope

If the function returns an object (rather than a reference), making a copy in the calling function is necessary [although optimisation steps may be taken that means that the object is written directly into the resulting storage where the copy would end up, according to the "as-if" principle].

In the sample code const MyClass myClass = GetMyClass(); this "copy" object is named myclass, rather than a temporary object that exists, but isn't named (or visible unless you look at the machine-code). In other words, whether you declare a variable for it, or not, there will be a MyClass object inside the function calling GetMyClass - it's just a matter of whether you make it visible or not.

Edit2:
The const reference solution will appear similar (not identical, and this really just written to explain what I mean, you can't actually do this):

 MyClass __noname__ = GetMyClass();
const MyClass &myclass = __noname__;

It's just that the compiler generates the __noname__ variable behind the scenes, without actually telling you about it.

By making a const MyClass myclass the object is made visible and it's clear what is going on (and that the GetMyClass is returning a COPY of an object, not a reference to some already existing object).

On the other hand, if GetMyClass does indeed return a reference, then it is certainly the correct thing to do.

IN some compilers, using a reference may even add an extra memory read when the object is being used, since the reference "is a pointer" [yes, I know, the standard doesn't say that, but please before complaining, do me a favour and show me a compiler that DOESN'T implement references as pointers with extra sugar to make them taste sweeter], so to use a reference, the compiler should read the reference value (the pointer to the object) and then read the value inside the object from that pointer. In the case of the non-reference, the object itself is "known" to the compiler as a direct object, not a reference, saving that extra read. Sure, most compilers will optimise such an extra reference away MOST of the time, but it can't always do that.

Prolonging life of a temporary object using const reference

The two links which you have referred are different in the sense that one shows the use of a local const reference and other shows the use of a class member const reference.

When we create local const references and refer to a temporary object then in this compiler extends the life of the temporary till the scope of local const reference.

Class member const reference pointing to temporary will lead to unexpected results as the life of the temporary object will not be extended beyond the constructor invoked to initialize class member reference. As explained in one of the answers the temporary will only survive till completion of the constructor.

Quoting the answer from:
Does a const reference prolong the life of a temporary?

The lifetime extension is not transitive through a function argument. §12.2/5 [class.temporary]:

The second context is when a reference is bound to a temporary. The temporary to which the reference is bound or the temporary that is the complete object to a subobject of which the temporary is bound persists for the lifetime of the reference except as specified below. A temporary bound to a reference member in a constructor’s ctor-initializer (§12.6.2 [class.base.init]) persists until the constructor exits. A temporary bound to a reference parameter in a function call (§5.2.2 [expr.call]) persists until the completion of the full expression containing the call.

If you analyze it correctly you will realize that in both cases the life of temporary is extended till the scope from where the references are initialized is valid. As soon as the scope from where reference goes out of scope the temporary becomes invalid.

For local const reference, scope is inside a function from where it is being initialized to a temp.
For class member const reference, scope is constructor where it is being initialized to a temp.

You should also read this GOTW article:
https://herbsutter.com/2008/01/01/gotw-88-a-candidate-for-the-most-important-const/

const reference to a temporary object becomes broken after function scope (life time)

The lifetime-extension of a temporary object can be performed only once, when the temporary object gets bound to the first reference. After that, the knowledge that the reference refers to a temporary object is gone, so further lifetime extensions are not possible.

The case that is puzzling you

A const& refnop = A(a).nothing();

is similar to this case:

A const& foo(A const& bar)
{
return bar;
}
//...
A const& broken = foo(A());

In both cases, the temporary gets bound to the function argument (the implicit this for nothing(), bar for foo()) and gets its lifetime 'extended' to the lifetime of the function argument. I put 'extended' in quotes, because the natural lifetime of the temporary is already longer, so no actual extension takes place.

Because the lifetime extension property is non-transitive, returning a reference (that happens to refer to a temporary object) will not further extend the lifetime of the temporary object, with as result that both refnop and broken end up referring to objects that no longer exist.

Why returning a const reference via delegation gets a segmentation fault in c++, while without the delegation just be fine

It's not the delegation, per se, that's the problem, but the function to which you delegate: that function returns a reference to a local object, which will cease to exist by the time the calling routine gets hold of it (or tries to).

You code, taken as is and compiled with clang-cl (Visual Studio 2019) gives the following:

warning : returning reference to local temporary object
[-Wreturn-stack-address]

Does const reference prolong the life of a temporary object returned by a temporary object?

The lifetime extension only applies when a reference is directly bound to that temporary.

For example, initializing another reference from that reference does not do another extension.

However, in your code:

std::string const& foo = aBar.getTemporaryObject1().getTemporaryObject2();

You are directly binding foo to the return value of getTemporaryObject2() , assuming that is a function that returns by value. It doesn't make a difference whether this was a member function of another temporary object or whatever. So this code is OK.

The lifetime of the object returned by getTemporaryObject1() is not extended but that doesn't matter (unless getTemporaryObject2's return value contains references or pointers to that object, or something, but since it is apparently a std::string, it couldn't).



Related Topics



Leave a reply



Submit