通过绑定到引用成员缩短了 C++ 临时变量的生命周期?

C++ temporary variable lifespan shortened by binding to a reference member?

直到现在,我 运行 假设临时对象在包含它的 完整表达式 末尾被销毁。我最近看到了规范的 [class.temporary]/5 部分,其中讨论了将临时对象分配给引用时发生的异常。在大多数情况下,除了 [class.temporary]/5:

中的一种特殊情况外,这似乎总是会延长临时变量的寿命
  • A temporary bound to a reference member in a constructor’s ctor-initializer (12.6.2) persists until the constructor exits.
  • A temporary bound to a reference parameter in a function call (5.2.2) persists until the completion of the full-expression containing the call.

如果我正在阅读这篇文章,这表明如果构造函数引用它,则此类对象的生命周期可能比 完整表达式 更短。

案例研究:

struct A
{
   A(int);
   ~A();

};

struct B
{
    B(const A& a)
    : memberRef(a)
    { }

    ~B();

    const A& memberRef;
};

// Just a few operators to use in my full expression
int operator+(const A&, const A&);
int operator+(const A&, const B&);

void testCase()
{
   int case1 = A(1) + A(2);
   int case2 = A(3) + B(A(4));
}

我给每个 A 构造函数一个不同的参数,以便于引用创建为 A1A2A3BA4.

在情况 1 中,A1A2 以任意顺序构造,然后进行加法。 A1A2 的生命周期受函数调用引用参数规则的约束。它们被扩展为包含加法运算符的完整表达式。

必须以相反的顺序调用析构函数,也在 [class.temporary]/5:

If the lifetime of two or more temporaries to which references are bound ends at the same point, these temporaries are destroyed at that point in the reverse order of the completion of their construction.

所以这告诉我必须在 A1A2A2 中调用这些对象的析构函数A1,具体取决于编译器选择构造对象的顺序。到目前为止一切顺利。

对我来说比较麻烦的是case2。如果我没看错的话,因为 A4 绑定到传递给 B::B(const A&) 的引用,它的生命周期现在在该构造函数结束时结束, 而不是表达式结束.

这向我暗示析构函数可以称为 A4A3BA4A4BA4A3。然而,A4 的析构函数必须始终排在第一位,因为它出现在 BA4 的构造函数的末尾,而不是在完整的构造函数的末尾表达式。

说明析构函数不可能按照A3,BA4,A4的顺序调用因为A4的寿命需要缩短

我是否正确阅读了规范?如果是这样,这条规则的基本原理是什么?对我来说,让传递给构造函数的临时对象与传递给函数调用的临时对象一样长的时间似乎更自然,但看起来规范编写者努力制定了其他规则。

你在两者之间使用了错误的项目符号。

This one:

A temporary bound to a reference member in a constructor's ctor-initializer ([class.base.init]) persists until the constructor exits.

不适用于此处。我们没有临时绑定到 ctor-initializer 中的引用成员。那种情况更像是:

struct B
{
    B()
    : memberRef(A(2)) // <== 
    { }

    ~B();

    const A& memberRef;
};

我们的情况正好是this one:

A temporary bound to a reference parameter in a function call ([expr.call]) persists until the completion of the full-expression containing the call.

我们有一个临时的 (A(4)) 绑定到构造函数中的引用参数(构造函数调用仍然是函数调用,我们绑定的参数是 a B(const A& a)), 所以临时文件一直持续到 full-expression.

完成

换句话说,您所展示的内容中没有悬空引用。所有这些关于将临时对象绑定到引用的规则都是关于生命周期 extension。 None 其中缩短了寿命。