寿命延长、prvalue和xvalue

7

根据这个问题的广受认可的答案(rvalue引用允许悬空引用吗?),似乎像问题中那样将xvalues分配给rvalue引用lvalue时,它们的生命周期并未得到延长。然而,当我这样做时:

#include <iostream>

using namespace std;

class Something {
public:
    Something() {
        cout << "Something()" << endl;
    }
    Something(const Something&) {
        cout << "Something(const Something&)" << endl;
    }
    Something(Something&&) {
        cout << "Something(Something&&)" << endl;
    }
    ~Something() {
        cout << "~Something()" << endl;
    }

    int a;
};

Something make_something() {
    return Something{};
}

int main() {
    auto&& something = make_something().a;

    return 0;
}

调用 make_something 函数返回的对象的生命周期被延长,尽管根据http://en.cppreference.com/w/cpp/language/value_category(xvalues解释中的第三个符号表示我上面访问成员所使用的是xvalue)make_something().a 是一个 xvalue。

a.m 表示对象表达式的成员,其中 a 是一个 rvalue,m 是非引用类型的非静态数据成员。

如果值类别不能决定 rvalue 的生命周期何时被延长,那么是什么决定呢?我很难理解 C++ 中 rvalue 何时会被延长生命周期。
2个回答

7
续命并不关心值类别。根据[class.temporary]/p6所述:
引用绑定的临时变量或引用绑定的子对象的完整对象,其生命周期与引用一致。
重点强调。
这里没有说明被引用表达式的值类别。
决定临时变量是否被延长的正是上述规则(以及其他一些规则)。
但这并不能解释为什么将std::move()添加到被分配给引用的临时变量周围不会延长其生命周期。
std::move不是C++中由编译器定义的神奇构造。它是一个函数调用,因此它的行为与任何其他C++函数调用没有区别。
那么,如果你有std::move(Type()),那意味着什么?这意味着你将创建一个临时变量,将其绑定到std::move的参数上,然后调用该函数,该函数将返回某些内容。
根据[class.temporary]/p6所述,将临时变量绑定到函数参数意味着该临时变量的生命周期被固定为创建它的完整表达式的生命周期(如果没有这条规则,那么该临时变量必须在函数调用结束时被销毁,因为那是引用生命周期的结束)。
无论函数做什么、说什么或者暗示什么,都没有关系。编译器是否可以内联代码并确定返回值是来自临时变量的参数引用也无关紧要。该临时变量的生命周期被固定为表达式,而不是被延长。

但这并不能解释为什么在临时变量周围添加std::move()不会延长引用的生命周期。 - Curious
@Curious 嗯...为什么不呢?你真的想过在临时变量周围添加 "std::move()" 到底意味着什么吗? - Nicol Bolas
也许还不够,我只是认为它将表达式转换为可移动的xvalue,因此可以绑定到rvalue引用。这里有什么我错过的吗? - Curious
“temporaries”在摘录中提到的包括但不限于prvalues,而std::move()的结果不被视为“临时变量”,因此其生命周期不会延长。xvalues可以像我在上面的问题中一样成为“临时变量”。 - Curious
5
@Curious: “std::move()返回值的生命周期不会被延长。” 值本身没有生命周期,根本不存在“值的生命周期”。对象才有生命周期,临时对象可以有其生命周期被延长。move函数的返回值是一个引用,而不是一个对象。请注意,该引用所绑定的对象可能会在其他地方声明并分配内存。 - Nicol Bolas
显示剩余10条评论

1
如果值类别不能确定rvalue的生命周期何时被延长,那么什么决定了?我很难理解在C++中rvalue的生命周期何时被延长。
请注意,值类别描述的是表达式而不是对象。值类别(xvalue、prvalue或其他)不会以任何方式被扩展。只有对象才有生命周期。
从n4296标准草案中可以看到:
§12.2.1
临时类类型在各种上下文中创建:将引用绑定到prvalue(8.5.3)、返回prvalue(6.6.3)、创建prvalue的转换(4.1、5.2.9、5.2.11、5.4)、抛出异常(15.1)和某些初始化(8.5)。
  • §12.2.4

    有两种情况下,临时对象的销毁时间不同于完整表达式的结束时间。[...]
    第二种情况是当一个引用被绑定到一个临时对象时。根据上面引用的上下文,被引用绑定的临时对象或者是被引用绑定子对象的完整临时对象会持续到该引用的生命周期结束。

注意:由于第一种情况与问题的相关性较小,因此我没有引用它。斜体强调由我添加。
因此,函数表达式makesomething()的值类别是创建一个类类型临时对象的prvalue,根据上面引用的第一段。
makesomething().a访问一个临时的、完整的子对象。根据上面引用的第二种情况,将这个临时对象绑定到一个引用上会导致其寿命延长。


子对象 a 的生命周期与之前创建的临时对象的生命周期相耦合,使其成为一个即将过期的值(xvalue)。如果不通过将其绑定到引用来延长其生命周期,则它将与临时类对象一起被销毁。因此,在这种情况下,在 ; 之后。

你能同时粘贴一下§12.2.4中提到的第一个上下文吗? - Curious
@Curious,这只是一种微不足道的语言细微差别。如果你感兴趣,可以在标准中查找它。将其添加到答案中会令人困惑。 - clickMe
@Curious 看看 Nicol Bolas 的这个答案吧。对我来说,它很清楚地阐明了这个主题 https://dev59.com/QnA65IYBdhLWcg3w1SfU#9552880 - clickMe
那是我在阅读有关xvalues、prvalues和glvalues的内容时读到的第一件事情之一 :) - Curious

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