什么决定了临时对象的生命周期何时延长为const引用或rvalue引用?

3

给定:

struct hurg { ... };

hurg get_hurg() { return hurg(); }
hurg&& get_mhurg() { return hurg(); }

我的理解和实验表明以下内容不是未定义行为(编辑:感谢答案,结果证明我错了,get_mhurg()示例确实是未定义行为):

{
    const hurg& a = get_hurg(); 
    hurg&& b = get_hurg();
    const hurg& c = get_mhurg(); 
    hurg&& d = get_mhurg();
    // do stuff with a, b, c, d
}
// a, b, c, d are now destructed

也就是说,由get_hurg()get_mhurg()返回的暂时对象hurg的生命周期会一直延续到作用域的结束。

但在此示例中(来自这里的函数):

template <typename T>
auto id(T&& x) -> decltype(auto) { return decltype(x)(x); }    

使用方法如下:
{
    const hurg& x = id(hurg()); 
    // the hurg() 'x' refers to is already destructed

    hurg&& y = id(hurg());
    // the hurg() 'y' refers to is already destructed

    // undefined behavior: use 'x' and 'y'
}

在这种情况下,hurg 的生命周期不会被延长。
一般来说,什么决定了临时对象的生命周期何时被延长?特别地,在何时将函数的结果绑定到const lvalue引用或rvalue引用是安全的?
更具体地说,在id案例中究竟发生了什么?
2个回答

6

来自[class.temporary]:

有两种情况下,临时对象的销毁时间不同于完整表达式的结束时间。 第一种情况是当调用默认构造函数初始化数组元素时[...]

第二种情况是当引用绑定到临时对象时。引用所绑定的临时对象或引用所绑定子对象的完整对象将一直持续到引用的生命周期结束,但以下情况除外:
(5.1) — 临时对象作为引用参数传递给函数调用(5.2.2)时,在包含该调用的 完整表达式 完成之前一直存在。
(5.2) — 临时对象作为函数返回语句 (6.6.3) 的返回值绑定时,生命周期不会得到延长;在返回语句中的 完整表达式 结束时,该临时对象将被销毁。
(5.3) — 临时对象在 new-initializer (5.3.4) 中绑定到引用上时,在包含该 new-initializer完整表达式 完成之前一直存在。

因此,有两个问题。首先,get_mhurg 是未定义的行为。返回的临时对象的生命周期不会得到延长。其次,传递给 id 的临时对象持续到包含函数调用的完整表达式结束,但是不再延长。与 get_mhurg 一样,该临时对象未经过延长。因此,这也是未定义的行为。


4
我的理解和实验表明以下内容不是未定义的行为:
嗯,你的理解和实验是错误的。聪明人的提示:不要尝试未定义的行为。例如,您的实验可能会随机地指示它是定义良好的行为,这是未定义行为的可能结果之一。
使用get_mhurg()的返回值是未定义行为。您正在访问超出创建范围的临时hurg()。
id的情况只是显示了寿命延长非常愚蠢。
具体来说,寿命延长仅适用于直接的值。它从不适用于任何类型的引用。必须使用一个值初始化参考。id不返回值,因此不会应用寿命延长。

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