简体   繁体   中英

The lifetime of a temporary to which several references are bound in C++

The C++ standard draft N4296 says

[class.temporary/5] 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 of a subobject to which the reference is bound persists for the lifetime of the reference except...

So I want to know what happens if two or more references are bound to a temporary. Is it specific in the standard ? The following code may be an example:

#include <iostream> //std::cout
#include <string>   //std::string
const std::string &f() {
    const std::string &s = "hello";
    static const std::string &ss = s;
    return ss;
}
int main() {
    const std::string &rcs = f();
    std::cout << rcs; //empty output
                      //the lifetime of the temporary is the same as that of s
    return 0;
}

If we change the bounding order, the case is different.

#include <iostream> //std::cout
#include <string>   //std::string
const std::string &f() {
    static const std::string &ss = "hello";
    const std::string &s = ss;
    return ss;
}
int main() {
    const std::string &rcs = f();
    std::cout << rcs; //output "hello"
                      //the lifetime of the temporary is the same as that of ss
    return 0;
}

The compilation is done on Ideone.com.

I guess [class.temporary/5] only holds when the first reference is bound to the temporary, but I cannot find an evidence in the standard.

This is a defect in that section that I reported as http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1299 .

The proposed resolution is to add a term "temporary expressions". Life-time extension only happens for objects referred to by temporary expressions.

Here's my original report which I privately emailed. I think it makes clear what it's about

In the model of the Standard, there appears to be a distinction about temporary objects, and temporary expressions.

Temporary objects are created by certain operations operations, like functional casts to class types. Temporary objects have a limited specified lifetime.

Temporary expressions are expressions that are so attributed because they are used to track whether or not an expression refers to a temporary object for the purpose of determining whether or not the lifetime of their referent is lengthened when bound by a reference. Temporary expressions are compile time entities.

Several paragraphs refer to "temporaries", but do not explicitly specify whether they refer to temporary objects referred to by arbitrary expressions, or whether they refer only to temporary expressions. The paragraphs about RVO (paragraph 12.8p31) use "temporary" in the sense of temporary objects (they say such things like "temporary class object that has not been bound to a reference"). The paragraphs about lifetime lengthening (sub-clause 12.2) refer to both kinds of temporaries. For example, in the following, "*this" is not regarded as a temporary, even though it refers to a temporary

 struct A { A() { } A &f() { return *this; } void g() { } }; // call of g() is valid: lifetime did not end prematurely // at the return statement int main () { A().f().g(); } 

As another example, core issue 462 handles about temporary expressions (making a comma operator expression a temporary, if the left operand was one). This appears to be very similar to the notion of "lvalue bitfields". Lvalues that track along that they refer to bitfields at translation time, so that reads from them can act accordingly and that certain reference binding scenarios can emit diagnostics.

What you need to know is that a reference return type does not count as a temporary for the purposes of this section, and will never cause lifetime extension.

(Pedantic note: The standard requires that the reference be bound to an xvalue , not merely to a temporary. The second reference is bound via an lvalue, not an xvalue.)

Your first example returns a dangling reference -- the cout line is undefined behavior. It could print Hello! and that would prove nothing.

Here's a simpler example:

template<class T>
const T& ident(const T& in) { return in; }

int main(void)
{
    const X& ref1 = X(1); // lifetime extension occurs
    const X& ref2 = ident(X(2)); // no lifetime extension
    std::cout << "Here.\n";
}

The order of construction and destruction is:

X(1)
X(2)
~X() for X(2) object
"Here." is printed
~X() for X(1) object

The first function presented in the question,

const std::string &f() {
    const std::string &s = "hello";
    static const std::string &ss = s;
    return ss;
}

yields Undefined Behaviour if the returned reference is used. The referred to object ceases to exist when the first call of the function returns. And on subsequent calls ss is a dangling reference .

The context of the standard's lifetime extension paragraph is

There are two contexts in which temporaries are destroyed at a different point than the end of the full-expression

Ie, this is all about about temporaries that otherwise would be destroyed at the end of the full-expression producing them.

One of the two contexts is, with four noted exceptions,

… when a reference is bound to [such a] a temporary

In the code above the temporary std::string produced by the full-expression "hello" is bound to the reference s and lifetime extended to the scope of s , which is the function body.

The subsequent declaration and initialization of a static reference ss does not involve a full-expression that creates temporary. Its initializer expression s is not a temporary: it's a reference to a local. Hence it's outside the context covered by the lifetime extension paragraph.

But how do we know that that's what's meant? Well, keeping track of whether a reference dynamically refers to something that originally was a temporary, is non-computable for the general case, and the C++ language standard does not involve such far fetched concepts. So it's simple, really.


An IMHO more interesting case, wrt. the formal rules, is

 #include <string> #include <iostream> using namespace std; template< class Type > auto temp_ref( Type&& o ) -> T& { return o; } auto main() -> int { auto const& s = temp_ref( string( "uh" ) + " oh!" ); cout << s << endl; } 

I contend that there's no lifetime extension here, and that the output statement using reference s is using a dangling reference, with UB as a result.

And I think that this conclusion, unlike the OP's choice of example, cannot be argued solely on the basis of the standard's wording, because (it seems to me that) the standard's wording is a bit defective. That it fails to make an exception for reference types . But, I might be wrong, and if I learn that I'll update this answer to reflect that new understanding.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM