正在通过const-ref未定义的行为捕获新构造的对象

Is capturing a newly constructed object by const ref undefined behavior

本文关键字:新构造 对象 const-ref 未定义      更新时间:2023-10-16

以下(人为的例子(可以吗?还是它是未定义的行为:

// undefined behavior?
const auto& c = SomeClass{};
// use c in code later
const auto& v = c.GetSomeVariable();

它是安全的。Const-ref延长了临时的使用寿命。该范围将是const参考的范围

临时对象的生存期可以通过绑定到常量左值引用或右值引用(从C++11开始(,请参阅有关详细信息,请参阅初始化。

引用绑定到临时对象或子对象时其中,临时的寿命被延长以匹配引用的生存期,以下例外

  • return语句中函数返回值的临时绑定没有扩展:它在返回表达式。这样的函数总是返回一个悬空参考
  • 到构造函数初始值设定项列表中引用成员的临时绑定仅持续到构造函数退出,而不是只要对象存在。(注意:这种初始化是错误的,如DR 1696(
  • 函数调用中引用参数的临时绑定一直存在,直到包含该函数的完整表达式结束调用:如果函数返回一个引用,则该引用将超过完整的表达式,它就变成了一个悬空的引用
  • 新表达式中使用的初始值设定项中引用的临时绑定一直存在,直到包含新表达式,而不是初始化对象那么长。如果初始化的对象的寿命超过其引用成员的完整表达式成为悬空引用
  • 临时绑定到使用直接初始化语法初始化的聚合的引用元素中的引用(括号(与列表初始化语法相反(大括号(一直存在到末尾包含初始值设定项的完整表达式的。 struct A { int&& r; }; A a1{7}; // OK, lifetime is extended A a2(7); // well-formed, but dangling reference

通常,临时的生命周期不能通过"传递"来进一步延长on":第二个引用,从临时绑定,不影响其使用寿命。

正如@Konrad Rudolph所指出的(见上文最后一段(:

"如果c.GetSomeVariable()返回对本地对象的引用,或者它本身正在延长某个对象的生存期的引用,则生存期延长不会生效">

由于寿命延长,这里应该没有问题。新构建的对象将一直存在,直到引用超出范围。

是的,这是完全安全的:绑定到const引用将临时的生存期延长到该引用的范围。

请注意,行为不是传递性。例如,使用

const auto& cc = []{
const auto& c = SomeClass{};
return c;
}();

cc悬挂。

这是安全的

[class.temporary]/5:有三种上下文中,临时性在与完整表达式结尾不同的点被销毁[..]

[class.temporary]/6:第三个上下文是当引用绑定到临时对象时引用所绑定的临时对象或引用所绑定子对象的完整对象的临时对象在引用的生存期内持续存在,如果引用所绑定到的glvalue是通过以下方法之一获得的:[此处有很多东西]

在这种特定情况下是安全的。然而,请注意,并不是所有临时变量都可以通过常量引用安全捕获。。。例如

#include <stdio.h>
struct Foo {
int member;
Foo() : member(0) {
printf("Constructorn");
}
~Foo() {
printf("Destructorn");
}
const Foo& method() const {
return *this;
}
};
int main() {
{
const Foo& x = Foo{};        // safe
printf("here!n");
}
{
const int& y = Foo{}.member; // safe too (special rule for this)
printf("here (2)!n");
}
{
const Foo& z = Foo{}.method(); // NOT safe
printf("here (3)!n");
}
return 0;
}

z获得的引用使用起来不安全,因为在到达printf语句之前,临时实例将在完整表达式结束时销毁。输出为:

Constructor
here!
Destructor
Constructor
here (2)!
Destructor
Constructor
Destructor
here (3)!