在C++中绑定了多个引用的临时的生存期

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

本文关键字:引用 生存期 C++ 绑定      更新时间:2023-10-16

C++标准草案N4296说

[class.temporary/5]第二个上下文是引用绑定到临时的时。引用绑定到的临时对象或引用绑定到子对象的完整对象的临时对象在引用的生存期内持续存在,但。。。

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

#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]只有当第一个引用绑定到临时引用时才成立,但我在标准中找不到证据。

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

拟议的决议是增加一个术语"临时用语"。寿命延长只发生在临时表达式引用的对象上。

这是我私下发邮件的原始报告。我认为它清楚地表明了的意义

在本标准的模型中,似乎对临时对象和临时表达式。

临时对象是由某些操作创建的,例如函数型转换为类类型。临时对象的指定的使用寿命。

临时表达式是由于它们用于跟踪表达式是否引用临时对象,用于确定当被引用绑定时,它们的引用的生存期会延长。临时表达式是编译时实体。

有几段提到"临时措施",但没有明确指出指定它们是否引用由引用的临时对象任意表达式,或者它们是否仅引用临时表达式表达式。关于RVO的段落(第12.8p31段)使用临时物体意义上的"临时"(他们说这样的话比如"尚未绑定到引用的临时类对象")。关于寿命延长的段落(第12.2款)参考两种临时措施。例如,在以下内容中,"*this"是不被视为临时的,即使它指的是临时的

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(); }

作为另一示例,核心问题462处理关于临时表达式的问题(如果左操作数为是一个)。这似乎与"左值"的概念非常相似bitfields"。跟踪的Lvalue表示翻译时间,这样读者就可以采取相应的行动某些引用绑定场景可以发出诊断。

您需要知道的是,在本节中,引用返回类型不算作临时类型,并且永远不会导致寿命延长。

(教学说明:标准要求引用绑定到xvalue,而不仅仅是临时的。第二个引用通过lvalue绑定,而不是xvalue。)

第一个示例返回一个悬空引用——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

问题中提出的第一个函数

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

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

标准寿命延长段的上下文

C++11§12.2/4

"有两种情况下,临时性在与全表达式结尾不同的点被销毁

也就是说,这一切都是关于临时性的,否则这些临时性会在产生它们的完整表达结束时被破坏。

除了四个注意到的例外,这两种情况之一是

C+11§12.2/5

"…当引用绑定到临时时

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

静态引用ss的后续声明和初始化不涉及创建临时的完整表达式。它的初始值设定项表达式s不是临时的:它是对局部的引用。因此,这超出了延长寿命条款所涵盖的范围。

但是我们怎么知道这就是我们的意思呢?好吧,跟踪引用是否动态地引用了最初是临时的东西,在一般情况下是不可计算的,C++语言标准也不涉及这些牵强的概念。所以这很简单,真的。


根据形式规则,IMHO更有趣的案例是

#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选择的例子不同,这一结论不能仅仅基于标准的措辞进行论证,因为(在我看来)标准的措辞有点缺陷。它未能为引用类型破例。但是,我可能错了,如果我知道了,我会更新这个答案,以反映这种新的理解。