Lifetime of a temporary object associated with a constant reference (method chain)

Consider the following snipet code:

#include <iostream> struct S { ~S() { std::cout << "dtor\n"; } const S& f(int i) const { std::cout << i << "\n"; return *this; } }; int main() { const S& s = S(); sf(2); } 

Output :

 2 dtor 

those. the lifetime of the object is distributed by reference, which is explained in the Herb article.

But if we change only one line of code and write:

 const S& s = S().f(1); 

a call to f (2) made on an already destroyed object:

Output :

 1 dtor 2 

Why did this happen? Does f () return the wrong type of "transient"?

+8
c ++ reference lifetime
source share
2 answers

When you write a function this way ...

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

... you instruct the compiler to return const S& , and you take the responsibility of ensuring that the object referenced has a lifetime suitable for the use of the caller. (“collateral” may be documentation of client use that works correctly with your design.)

Often - with a typical separation of the code into headers and implementation files - the f(int) const implementation will not even be visible to the calling code, and in such cases the compiler has no idea which reference S can be returned to, and also that S is temporary or not, so he has no reason to decide on the need to extend his life.

Besides the obvious options (for example, trusted clients for writing safe code, return by value, or a smart pointer), you should know about a more obscure option ...

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

& and && just before the function bodies overload f so that the && version is used if *this is movable, otherwise the & version is used. Thus, someone linking const & to f(...) called by the expiring object is bound to a new copy of the object and extends the life of the local const link, while the object does not expire (yet) the const link will be to the original object (which is still not guaranteed live until the link - some caution is required).

+4
source share

Why did this happen? Is f() return value is not the correct type "temporary"?

That's right, it is not. This is a somewhat controversial issue recently: the official definition of "temporality" is somewhat open.

In recent compilers, temporality expands. At first, it applies only to prvalue (non-reference) expressions, and user calls (the “dot operator”) apply to them. This now applies to expression expressions and array calls. Although you can write the move operation as static_cast< T && >( t ) , which saves time, just writing std::move( t ) will not.

I am working on a series of suggestions for a C ++ extension so that your example works as you would expect. There are some non-zero chances that this function may appear in C ++ 17.

+3
source share

All Articles