[英]Member rvalue references and object lifetime
自从我上次查看临时生命周期规则以来已经有一段时间了,我不记得成员右值引用如何影响生命周期。
例如,请使用以下两段代码:
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如何处理给定的例子? 三者之间有区别吗?
自98年以来,在任何版本的C ++中,临时终身扩展的规则都没有改变。我们可能有新的方式来表现临时工,但一旦存在,他们的生命周期就会被很好地理解。
应该注意的是,您的示例并不真正适用于任何类型的成员引用。 您正在使用临时调用函数,该参数是转发引用。 因此,有问题的临时函数绑定到函数参数引用,而不是成员引用。 该临时的生命周期将在调用该函数的表达式之后结束,就像任何临时传递给引用参数一样。
这个函数( forward_as_tuple
)最终将该引用存储在tuple
的事实是无关紧要的。 你用引用做什么不能改变它的生命周期。
再说一遍,这是C ++ 98,后来的版本都没有改变。
当您在构造函数初始化列表中的任何位置直接将临时绑定到引用时,将应用Lifetime扩展。 (注意:聚合初始化不是构造函数)
std::forward_as_tuple
是一个函数。 传递给它的任何临时值都不能超出当前行的生命周期。
默认情况下,Temporaries一直持续到当前行的结尾。 (究竟是什么位置实际上并不是当前行的结束)。 在您的两种情况下,它是当前行( ;
)的结尾,临时结束其生命周期。 这对于第一种情况来说足够长了; 在第二种情况下,临时性已经死亡,您的代码显示未定义的行为。
与此同时:
struct foo {
int&& x;
};
int main() {
foo f{3};
std::cout << f.x << "\n";
}
是完全明确的。 没有构造函数,我们将临时绑定到(rvalue)引用,从而延长了生命周期。
添加这个:
struct foo {
int&& x;
foo(int&& y):x(y) {}
};
要么
struct foo {
int&& x;
foo(int y):x((int)y) {}
};
它现在是UB。
第一个是因为我们在调用ctor时将临时值绑定到右值引用。 构造函数的内部是无关紧要的,因为没有临时绑定。 然后函数和临时的参数都超出了main
的范围。
第二个因为在构造函数初始化列表中将临时(int)y
0绑定到int&&x
的规则不会像在其他地方那样延长生命周期。
因为这是一个语言 - 律师问题。 终身延长的规则在[class.temporary]中。 从C ++ 11到C ++ 14到C ++ 17的措辞没有以与这个特定问题相关的方式改变。 规则是:
有[两/三† ]上下文,其中临时值在与完整表达式结束时不同的点被销毁。 第一个上下文是调用默认构造函数来初始化数组的元素[...] †
[second / third † ]上下文是指引用绑定到临时的。 绑定引用的临时对象或绑定引用的子对象的完整对象的临时对象在引用的生命周期内持续存在,除了:
- 绑定到函数调用(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);
因为临时函数对象的生命周期在语句初始化t
结束时结束。
请注意,这与成员右值引用无关。 如果我们有类似的东西:
struct Wrapper {
X&& x;
};
Wrapper w{X()};
那么临时X
的生命周期在w
的生命周期中持续存在,并且wx
不是悬空引用。 但那是因为没有函数调用。
† C ++ 17引入了第三个上下文,涉及复制一个数组,这里不相关。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.