成员重新值引用和对象生存期

Member rvalue references and object lifetime

本文关键字:对象 生存期 引用 新值 成员      更新时间:2023-10-16

我上次查看临时生存期规则已经有一段时间了,我不记得成员rvalue引用是如何影响的一生

例如,下面两段代码:

int main() 
{
std::get<0>(std::forward_as_tuple(
[](int v){ std::cout << v << std::endl; }
))(6);
return 0;
}

int main() 
{
auto t = std::forward_as_tuple(
[](int v){ std::cout << v << std::endl; }
);
std::get<0>(t)(6);
return 0;
}

如果成员右值引用不影响生存期规则,我希望第一个例子表现良好,而第二个例子未定义(因为包含lambda对象的完整表达式以第一个分号结尾)。

C++11、C++14和C++17如何处理给定的示例?这三者之间有区别吗?

当您直接将临时绑定到构造函数初始值设定项列表中以外的任何位置的引用时,将应用生存期扩展。(注意:聚合初始化不是构造函数)

CCD_ 1是一个函数。传递给它的任何临时项的生存期都不能延长到当前行之外。

默认情况下,临时性基本上会持续到当前行的末尾。(那个点实际上并不是当前行的末尾)。在您的两种情况下,它是当前行(;)的末尾,临时性将在此处结束其使用寿命。这对于第一种情况来说已经足够长了;在第二种情况下,临时的是死的,您的代码显示出未定义的行为。

同时:

struct foo {
int&& x;
};
int main() {
foo f{3};
std::cout << f.x << "n";
}

定义完美。没有构造函数,我们将临时绑定到(右值)引用,从而延长了生存期。

添加此:

struct foo {
int&& x;
foo(int&& y):x(y) {}
};

struct foo {
int&& x;
foo(int y):x((int)y) {}
};

现在是UB。

第一个是因为我们在调用ctor时将临时绑定到了一个右值引用。构造函数的内部是不相关的,因为那里没有直接绑定的临时对象。然后函数的参数和临时参数都超出了main的范围。

第二个原因是,将构造函数初始值设定项列表中的临时(int)y0绑定到int&&x的规则不会像在其他地方那样延长生存期。

自98年以来,在任何版本的C++中,临时生命周期扩展的规则都没有改变。我们可能有新的方式来表现暂时性,但一旦它们存在,它们的一生就会被很好地理解。

需要注意的是,您的示例并不真正适用于任何类型的成员引用。您正在使用临时调用一个函数,而该参数是一个转发引用。因此,有问题的临时绑定到函数参数引用,而不是成员引用。该临时函数的生存期将在调用该函数的表达式之后结束,就像传递给引用参数的任何临时函数一样。

该函数(forward_as_tuple)最终将该引用存储在tuple中这一事实无关紧要。您对引用所做的操作不会改变其使用寿命。

同样,这是C++98,并且没有一个后来的版本改变这一点。

因为这是一个语言律师问题。寿命延长的规则在[class.temporary]中。从C++11到C++14再到C++17的措辞没有以与这个特定问题相关的方式发生变化。规则是:

有[2/3个]上下文,其中临时性在与完整表达式结尾不同的点被破坏。第一个上下文是调用默认构造函数来初始化数组的元素[…]

[第二/第三]上下文是指引用绑定到临时的。引用所指向的临时绑定的或作为引用绑定到的子对象的完整对象的临时对象仍然存在除了:
-函数调用(5.2.2)中绑定到引用参数的临时对象将一直持续到完成包含调用的完整表达式的。

此表达式:

std::forward_as_tuple([](int v){ std::cout << v << std::endl; })

涉及将引用(forward_as_tuple中的参数)绑定到prvalue(lambda表达式),这在C++11/14中被明确提及为创建临时:的上下文

类类型的临时变量是在各种上下文中创建的:将引用绑定到prvalue,〔…〕

,在C++17中用表示

创建临时对象
(1.1)-当一个prvalue被物化,以便它可以用作glvalue(4.4)时,

无论哪种方式,我们都有一个临时对象,它绑定到函数调用中的引用,因此临时对象一直存在,直到包含该调用的完整epxression完成。

所以这没关系:

std::get<0>(std::forward_as_tuple(
[](int v){ std::cout << v << std::endl; }
))(6);

但这需要一个悬而未决的参考:

auto t = std::forward_as_tuple(
[](int v){ std::cout << v << std::endl; }
);
std::get<0>(t)(6);

因为临时函数对象的生存期在初始化CCD_ 9的语句结束时结束。

请注意,这与成员右值引用无关。如果我们有这样的东西:

struct Wrapper {
X&& x;
};
Wrapper w{X()};

则临时std::forward_as_tuple0的寿命持续到w的寿命并且w.x不是悬空参考。但那是因为没有函数调用。


C++17引入了第三个上下文,该上下文涉及复制数组,这在这里是无关的。