Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Consider the following code snipet:

#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(); 
    s.f(2);
}

Output:

2
dtor

I.e. object lifetime extends by reference which explained in Herb's article.

But, if we change just one line of code and write:

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

call of f(2) made on already destroyed object:

Output:

1
dtor
2

Why did this happen? Is f()'s return value not a correct type of "temporality"?

like image 659
αλεχολυτ Avatar asked Sep 01 '15 16:09

αλεχολυτ


People also ask

Does const reference extend lifetime?

In the by-reference case, we get a const Base& reference that refers to a Derived object. The entire temporary object, of type Derived , is lifetime-extended.

What is the lifetime of an object and how can you extend the lifetime of an object?

The lifetime of a temporary object may be extended by binding to a const lvalue reference or to an rvalue reference (since C++11), see reference initialization for details.

What is the lifetime of an object in c++?

C/C++ use lexical scoping. The lifetime of a variable or object is the time period in which the variable/object has valid memory. Lifetime is also called "allocation method" or "storage duration."

What is function chaining in c++?

Method chaining in C++ is when a method returns a reference to the owning object so that another method can be called.


2 Answers

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

like image 86
Tony Delroy Avatar answered Nov 15 '22 11:11

Tony Delroy


Why did this happen? Is f()'s return value not a correct type of "temporality"?

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

In recent compilers, temporality has been expanding. First it only applied to prvalue (non-"reference") expressions, and member accesses ("dot operator") applied to such expressions. Now it applies to cast expressions and array accesses as well. Although you can write a move operation as static_cast< T && >( t ), which will preserve temporality, simply writing std::move( t ) will not.

I'm working on a series of proposals to extend C++ so your example will work as you expected. There's some nonzero chance that the feature could appear in C++17.

like image 26
Potatoswatter Avatar answered Nov 15 '22 13:11

Potatoswatter