C++ Constant Reference Lifetime (Container Adaptor)

C++ constant reference lifetime (container adaptor)

According to the C++03 standard, a temporary bound to a reference has differing lifetimes depending on the context. In your example, I think the highlighted portion below applies (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.

So while binding a temporary is an advanced technique to extend the lifetime of the temporary object (GotW #88: A Candidate For the "Most Important const"), it apparently won't help you in this case.

On the other hand, Eric Niebler has an article that you may be interested in that discusses an interesting (if convoluted) technique that could let your class's constructors deduce whether a temporary object (actually an rvalue) has been passed to it (and therefore would have to be copied) or a non-temporary (lvalue) as been passed (and therefore could potentially safely have a reference stashed away instead of copying):

  • Conditional Love: FOREACH Redux

Good luck with it though - every time I read the article, I have to work through everything as if I've never seen the material before. It only sticks with me for a fleeting moment...

And I should mention that C++0x's rvalue references should make Niebler's techniques unnecessary. Rvalue references will be supported by MSVC 2010 which is scheduled to be released in a week or so (on 12 April 2010 if I recall correctly). I don't know what the status of rvalue references is in GCC.

Lifetime of temporary object associated with const reference (method chaining)

When you write a function thus...

const S& f(int i) const { std::cout << i << "\n"; return *this; }

...you're instructing the compiler to return a const S& and you are taking responsibility for ensuring the referenced object has a lifetime suitable for the caller's use. ("ensuring" may constitute documenting client usage that works properly with your design.)

Often - with typical separation of code into headers and implementation files - f(int) const's implementation won't even be visible to calling code, and in such cases the compiler has no insight regarding to which S a reference might be returned, nor whether that S is a temporary or not, so it has no basis on which to decide whether the lifetime needs to be extended.

As well as the obvious options (e.g. trusting clients to write safe code, returning by value or smart pointer), it's worth knowing about a more obscure option...

const S& f(int i) const & { ...; return *this; }
const S f(int i) const && { ...; return *this; }

The & and && immediately before the function bodies overload f such that the && version is used if *this is movable, otherwise the & version is used. That way, someone binding a const & to f(...) called on an expiring object will bind to a new copy of the object and have the lifetime extended per the local const reference, while when the object isn't expiring (yet) the const reference will be to the original object (which still isn't guaranteed live as long as the reference - some caution needed).

Lifetime of temporary object associated with const reference (method chaining)

When you write a function thus...

const S& f(int i) const { std::cout << i << "\n"; return *this; }

...you're instructing the compiler to return a const S& and you are taking responsibility for ensuring the referenced object has a lifetime suitable for the caller's use. ("ensuring" may constitute documenting client usage that works properly with your design.)

Often - with typical separation of code into headers and implementation files - f(int) const's implementation won't even be visible to calling code, and in such cases the compiler has no insight regarding to which S a reference might be returned, nor whether that S is a temporary or not, so it has no basis on which to decide whether the lifetime needs to be extended.

As well as the obvious options (e.g. trusting clients to write safe code, returning by value or smart pointer), it's worth knowing about a more obscure option...

const S& f(int i) const & { ...; return *this; }
const S f(int i) const && { ...; return *this; }

The & and && immediately before the function bodies overload f such that the && version is used if *this is movable, otherwise the & version is used. That way, someone binding a const & to f(...) called on an expiring object will bind to a new copy of the object and have the lifetime extended per the local const reference, while when the object isn't expiring (yet) the const reference will be to the original object (which still isn't guaranteed live as long as the reference - some caution needed).

Lifetime extension of temporary objects: what is the full expression containing a function call?

Yes, the whole std::copy_if call is the full-expression and the temporary std::vector<Stuff> will be destroyed only after the call returns.

This is different from by-value function parameters. If the constructor took a std::vector<Stuff> instead of a const std::vector<Stuff>&, then it would be implementation-defined whether the object lives that long or is destroyed immediately when the constructor returns.


A full-expression is one of the expressions listed in [intro.execution]/5. None of the specific cases (such as unevaluated operands, immediate invocations, etc.) apply here and so falling through, the relevant condition is:

an expression that is not a subexpression of another expression and that is not otherwise part of a full-expression.

The copy_if call expression is the only one in that statement to which this applies (outside of the lambda body).

What is the lifetime of the class data member which const reference to a rvalue?

The C++03 standard (Section "12.2/5 Temporary objects") answers your question aptly:

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.

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

c++ rvalue passed to a const reference

The return value has the lifetime of a temporary. In C++ that means the complete expression that created the type, so the destructor of MyType shouldn't be called until after the call to someFunc returns.

I'm curious at your 'is overwritten/freed'. Certainly calling delete on this object is not OK; it lives on the stack and deleting it would probably cause heap corruption. Also, overwriting/modifying it could also be bad. Your example uses a constant "C string"; on many compilers values like this are stored in read-only memory, so attempting to modify it later could cause a crash/access violation. (I'm not sure if Visual C++ does this optimization, however).

There is a big difference between passing a temporary by const versus mutable references. Creating a mutable reference to a temporary is not allowed by standard C++, and most compilers (including GCC) will reject it, though at least some versions of Visual C++ do allow it.

If you are passing it with a mutable reference, what you want to write is:

   MyType t = creatorFunc();
someFunc(t);

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



Related Topics



Leave a reply



Submit