延长临时工的使用寿命的理由是什么

What is the rationale for extending the lifetime of temporaries?

本文关键字:理由 是什么 临时工      更新时间:2023-10-16

在C++中,可以通过将临时值绑定到引用来延长其生存期:

Foo make_foo();
{
    Foo const & r1 = make_foo();
    Foo && r2 = make_foo();
    // ...
}             // both objects are destroyed here

为什么允许这样做?这解决了什么问题?

我在设计与进化中找不到对此的解释(例如6.3.2:Temporaries的寿命)。我以前也找不到任何关于这个的问题(这个问题最接近)。

此功能有些不直观,并且具有微妙的故障模式。例如:

Foo const & id(Foo const & x) { return x; }  // looks like a fine function...
Foo const & r3 = id(make_foo());             // ... but causes a terrible error!

为什么一些可以如此容易和无声地被滥用的东西是语言的一部分?


更新:这一点可能足够微妙,需要澄清:我不反对使用"引用与临时内容绑定"的规则。这一切都很好,并允许我们使用隐含的con­ver­绑定到引用时的sions。我想问的是,为什么临时的寿命会受到影响。我可以说,"完整表达式结束前的生存期"的现有规则已经涵盖了使用临时参数调用函数的常见用例。

简单的答案是,您需要能够将临时与const引用绑定,如果没有该功能,则需要大量的代码重复,函数的左值或值参数采用const&,或右值参数采用by-value。一旦您需要,该语言就需要定义一些语义,以确保临时的生存期至少与引用的生存期一样长。

一旦您接受引用可以在一个上下文中绑定到右值,为了保持一致性,您可能需要扩展规则以允许在其他上下文中进行相同的绑定,并且语义实际上是相同的。临时生存期将延长,直到引用消失(无论是函数参数还是局部变量)。

另一种选择是允许在某些上下文中绑定(函数调用)但不允许全部绑定(本地引用)的规则,或者允许两者并在后一种情况下始终创建悬挂引用的规则。


删除了答案中的引号,留在这里,这样评论仍然有意义:

如果你看一下标准中的措辞,就会发现一些关于这种预期用途的提示:

12.2/5[段落中间][…]函数调用(5.2.2)中与引用参数的临时绑定将持续到包含该调用的完整表达式完成为止。[…]

正如Bjarne Stroustrup(原设计师)在2005年的一篇clc++帖子中所解释的那样,这是为了统一规则。

参考文献的规则是最通用和统一的可以找到。在自变量和局部引用的情况下临时生命,只要它所绑定的引用。一明显的用途是作为深度嵌套的循环。例如:

for (int i = 0; i<xmax; ++i)
    for (int j = 0; j< ymax; ++j) { 
        double& r = a[i][j]; 
        for (int k = 0; k < zmax; ++k) { 
           // do something with a[i][j] and a[i][j][k] 
        }
    } 

这可以提高可读性和运行时性能。

事实证明,它对于存储从引用类型派生的类的对象非常有用,例如在最初的Scopeguard实现中。


在2008年clc++的一篇帖子中,James Kanze提供了更多细节:

该标准规定了必须调用析构函数的确切时间。之前然而,标准是ARM(以及早期的语言规范)相当宽松:析构函数可以在临时大括号已"使用",并且在下一个右大括号之前。

("ARM"是由(IIRC)Bjarne Stroustrup和Margareth Ellis编写的带注释的参考手册,在第一个ISO标准之前的过去十年中,该手册是事实上的标准。不幸的是,我的副本埋在一个盒子里,在很多其他盒子下面,在外屋里。所以我无法核实,但我相信这是正确的。)

因此,与其他许多方面一样,寿命延长的细节在标准化过程中得到了磨练和完善。

既然詹姆斯在对这个答案的评论中提出了这一点:完美无法及时影响比约恩延长寿命的理由。


类似Scopeguard的代码示例,其中引用的临时绑定是派生类型的完整对象,其派生类型析构函数在末尾执行:

struct Base {};
template< class T >
struct Derived: Base {};
template< class T >
auto foo( T ) -> Derived<T> { return Derived<T>(); }
int main()
{
    Base const& guard = foo( 42 );
}

我在SO的某个地方发现了一个有趣的终身扩展应用程序

寿命延长允许我们使用不动类型的prvalues。

例如:

struct Foo
{
    Foo(int, bool, char);
    Foo(Foo &&) = delete;
};

无法复制或移动类型Foo。然而,我们可以有一个函数返回类型为Foo:的prvalue

Foo make_foo()
{
    return {10, false, 'x'};
}

然而,我们不能构造一个用返回值make_foo初始化的局部变量,所以通常情况下,调用该函数会创建一个立即销毁的临时对象。寿命扩展允许我们在整个范围内使用临时对象:

auto && foo = make_foo();