在 C++ 中绑定多个引用的临时对象的生命周期

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

C++ 标准草案 N4296 说

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

所以我想知道如果两个或多个引用绑定到一个临时文件会发生什么。标准中是否具体?下面的代码可能是一个例子:

#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;
}

如果我们改变边界顺序,情况就不同了。

#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;
}

编译于Ideone.com完成。

我猜 [class.temporary/5] 只有在 first 引用绑定到临时文件时才成立,但我在标准。

您需要知道的是,引用 return 类型在本节中不算作临时类型,永远不会导致生命周期延长。

(迂腐的注解:标准要求引用绑定到 xvalue,而不仅仅是临时值。第二个引用是通过左值而不是 xvalue 绑定的。 )

您的第一个示例 return 是一个悬空引用 -- cout 行是未定义的行为。它可以打印 Hello!,但那什么也证明不了。

这是一个更简单的例子:

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";
}

构建和销毁的顺序是:

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

这是我报告为 http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1299 的部分中的缺陷。

提议的决议是添加一个术语"temporary expressions"。生命周期延长只发生在临时表达式引用的对象上。

这是我私下通过电子邮件发送的原始报告。我认为它清楚地说明了它是关于什么的

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.

问题中出现的第一个函数,

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

如果使用返回的引用,会产生未定义的行为。当第一次调用函数returns时,引用的对象不复存在。在随后的调用中 ss 是一个 悬空引用 .

标准生命周期延长段落的上下文

C++11 §12.2/4

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

也就是说,这都是关于临时变量的,否则这些临时变量会在生成它们的完整表达式结束时被销毁。

两个上下文之一是,除了四个明显的例外,

C+11 §12.2/5

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

在上面的代码中,由完整表达式 "hello" 生成的临时 std::string 绑定到引用 s 并且生命周期扩展到 s 的范围,这是函数体。

静态引用的后续声明和初始化ss不涉及创建临时的完整表达式。它的初始化表达式 s 不是临时的:它是对本地的引用。因此,它不在生命周期延长段落所涵盖的范围内。

但是我们如何知道那是什么意思?好吧,跟踪引用是否动态引用最初是临时的东西,对于一般情况来说是不可计算的,C++ 语言标准不涉及这种牵强附会的概念。所以很简单,真的。


恕我直言,一个更有趣的案例。正式规则是

#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;
}

我认为这里没有生命周期延长,使用引用 s 的输出语句使用的是悬空引用,结果是 UB。

而且我认为这个结论与 OP 选择的示例不同,不能仅根据标准的措辞来争论,因为(在我看来)标准的措辞有点缺陷。它无法为 引用类型 设置例外。但是,我可能是错的,如果我知道我会更新这个答案以反映新的理解。