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

5

根据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]只有在第一个引用被绑定到临时变量时才有效,但我在标准中找不到证据。

2
您不能将临时对象绑定到多个引用上。在您的示例中,唯一的临时对象(= prvalue)是从“hello”创建的std::string对象,并且它被绑定到第一个引用变量上。第二个引用被绑定到评估包含第一个变量的id表达式所产生的左值上。 - Kerrek SB
1
@KerrekSB “无法将临时对象绑定到多个引用”…再猜猜看 http://coliru.stacked-crooked.com/a/edfbe82db89ea4ab - Lorah Attkins
@Kerrek SB [N4296/expr/5]说:“如果一个表达式最初的类型是'reference to T' (8.3.2, 8.5.3),在进一步分析之前,类型将被调整为T。该表达式指定由引用所表示的对象或函数,并且该表达式是一个lvalue或xvalue,具体取决于表达式。”因此,我认为第二个引用确实绑定到了临时变量上。 - xskxzr
1
这是来自cppreference的一句话(非规范性):“通常情况下,临时对象的生命周期不能通过“传递”进一步延长:从绑定临时对象的引用初始化的第二个引用不会影响其生命周期。” - David G
1
@Shenke:但临时变量必须是prvalue。如果它不是prvalue,那么它就不是临时的了。 - Kerrek SB
显示剩余4条评论
3个回答

5
这是一个我报告的那部分缺陷,链接为http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1299
建议的解决方案是添加一个术语“临时表达式”。仅当被临时表达式引用的对象才会发生生命周期延长。
这是我最初私下发送的报告。我认为它很清楚地说明了问题。

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.


@KerrekSB:原谅我只是在钓鱼(我对这个问题不太清楚),但有关将对象的一部分绑定到引用时延长其生命周期的措辞。那么,这种情况的一个例子是什么? - Cheers and hth. - Alf
@Cheersandhth.-Alf: 没错 :-) 话说,标准在这方面仍然相当不清楚,例如在绑定数组元素时:int && a = T()[4];using T = int[10];等。 - Kerrek SB
@Cheersandhth.-Alf 0 不是一个临时表达式(它不指向一个临时对象)。但它是一个 prvalue。 - Johannes Schaub - litb
对于类prvalues,我认为它们总是临时表达式(至少一旦将prvalue.member固定为非静态成员时不会成为xvalue。以前它是非临时prvalue),数组也是如此。 - Johannes Schaub - litb
@KerrekSB,我认为你上面关于X{}.a不再C++14中具有生命周期延长的说法是错误的。我们一直有一个特殊规则,即在临时对象上进行成员访问表达式的情况下,会延长成员对象的完整(临时)对象的生命周期。 - Johannes Schaub - litb
显示剩余8条评论

3
你需要知道的是,参考返回类型不会被视为临时变量,也不会导致生命周期延长。
(小题注:标准要求引用必须绑定到一个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

感谢 @0x499602D2: 发现问题,它没有展示我想要的。现在已经修复。 - Ben Voigt
关于xvalue的要求,我找不到相关信息。你能提供一个参考吗? - Cheers and hth. - Alf
@Alf:直接绑定与8.5.3的最后几段中涉及的xvalueprvalue(它们是xvalues)有关。临时生存期延长仅适用于直接绑定,尽管正如Johannes指出的那样,生存期延长规则省略了direct一词,但这只是暗示。 - Ben Voigt
@ᐅJohannesSchaub-litbᐊ:嗯,我的理解是8.5.3说表达式确实直接绑定,但进一步检查n4527中8.5.3的措辞似乎由于编辑不完整而出现了严重问题。 - Ben Voigt
@BenVoigt,这个“子弹”并不是所指的“最后一种情况”,而只是在它之前用某些规则限制了“否则:”情况。说“Foo不应该是bar”是直接或间接的绑定似乎没有意义。 - Johannes Schaub - litb
显示剩余4条评论

1

问题中呈现的第一个函数:

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++语言标准也不涉及这样遥远的概念。所以,这很简单。


就正式规则而言,我认为更有趣的案例是

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

在“它的初始化表达式s不是临时的:它是对本地变量的引用”中,您的意思是名称查找找到了一个本地变量,而不是C++中的引用概念,对吗? - Ben Voigt
我的意思是这个表达式不会产生临时对象。它本身不会创建一个临时对象,由于它和要初始化的对象类型一样,也不会被用来创建一个临时对象。 - Cheers and hth. - Alf

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接