延长临时寿命的理由是什么?

时间:2013-12-12 15:05:03

标签: c++ language-design

在C ++中,临时值的生命周期可以通过将其绑定到引用来扩展:

Foo make_foo();

{
    Foo const & r1 = make_foo();
    Foo && r2 = make_foo();

    // ...
}             // both objects are destroyed here

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

我在设计和演变中找不到相关的解释(例如6.3.2:Temporaries的生命时间)。我也无法找到任何关于此的问题(this one最接近)。

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

Foo const & id(Foo const & x) { return x; }  // looks like a fine function...

Foo const & r3 = id(make_foo());             // ... but causes a terrible error!

为什么某些东西可以如此容易而且默默地被滥用到语言的一部分?


更新:这一点可能非常微妙,值得澄清一下:我不反对使用“引用绑定临时”的规则。这一切都很好,并且允许我们在绑定引用时使用隐式转换。我要问的是为什么临时的生命周期会受到影响。为了扮演魔鬼的拥护者,我可以声称现有的“终身直到完全表达结束”的规则已经涵盖了使用临时参数调用函数的常见用例。

3 个答案:

答案 0 :(得分:14)

简单的答案是,你需要能够将一个临时值与一个const引用绑定,没有那个特性需要大量的代码重复,函数对于左值或值参数采用const&或者-value为rvalue参数。 一旦你需要,语言需要定义一些语义,以保证临时的生命周期至少与引用的生命周期一样长。

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

备选方案是允许在某些上下文中绑定的规则(函数调用)但不是所有(本地引用)或允许两者的规则,并且在后一种情况下始终创建悬空引用。


从答案中删除了引用,留在这里以便评论仍然有意义:

如果你看一下标准中的措辞,就会有一些提示:

<击>   

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

答案 1 :(得分:9)

2005年clc ++发布中的Bjarne Stroustrup (the original designer) explained it为统一规则。

  

参考规则只是最普遍和最统一的   能找到。在参数和本地引用的情况下,   临时生命,只要它的约束参考。一   明显的用法是作为复杂表达的简写   深层嵌套循环。例如:

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] 
        }
    } 
     

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

并且结果证明存储从引用类型派生的类的对象是有用的,例如,如在the original Scopeguard implementation中那样。


a clc++ posting in 2008中,詹姆斯坎泽提供了更多细节:

  

标准确切地说明必须在何时调用析构函数。之前   但标准是ARM(以及早期的语言规范)   相当宽松:析构函数可以在任何时候被调用   临时是“使用”,在下一个结束之前。

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

因此,与其他许多事情一样,终身扩展的细节在标准化过程中得到了磨练和完善。

由于詹姆斯在对这个答案的评论中提出了这一点:完美无法及时回到影响Bjarne延长寿命的理由。


类似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 );
}

答案 2 :(得分:2)

我在SO上发现了一个有趣的终身扩展应用程序。 (我忘了在哪里,当我找到它时,我会添加一个参考。)

Lifetime扩展允许我们使用固定类型的prvalues。

例如:

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

无法复制或移动类型Foo。但是,我们可以使用一个返回Foo类型的prvalue的函数:

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

然而,我们无法构造一个用make_foo的返回值初始化的局部变量,因此通常,调用该函数将创建一个立即销毁的临时对象。 Lifetime扩展允许我们在整个范围内使用临时对象:

auto && foo = make_foo();