与 const 引用关联的临时对象的生命周期(方法链接)

Lifetime of temporary object associated with const reference (method chaining)

考虑以下代码片段:

#include <iostream>

struct S {
    ~S() { std::cout << "dtor\n"; }
    const S& f(int i) const { std::cout << i << "\n"; return *this; }
};

int main() {
    const S& s = S(); 
    s.f(2);
}

Output:

2
dtor

即对象生命周期通过引用扩展,这在 Herb 的 article.

中有解释

但是,如果我们只更改一行代码并写成:

const S& s = S().f(1);

对已销毁对象的 f(2) 调用:

Output:

1
dtor
2

为什么会这样? f() 的 return 值不是 "temporality" 的正确类型吗?

Why did this happen? Is f()'s return value not a correct type of "temporality"?

对,不是。这是最近有点争议的问题:"temporality"的官方定义有点开放。

在最近的编译器中,时间性一直在扩大。首先,它仅适用于纯右值(非"reference")表达式,成员访问("dot operator")适用于此类表达式。现在它也适用于转换表达式和数组访问。虽然您可以将移动操作写成 static_cast< T && >( t ),这将保留时间性,但简单地写成 std::move( t ) 不会。

我正在研究 series of proposals 以扩展 C++,因此您的示例将按您的预期工作。该功能出现在 C++17 中的可能性非零。

当你这样写一个函数时...

const S& f(int i) const { std::cout << i << "\n"; return *this; }

...您正在指示编译器 return a const S& 并且您负责确保引用的对象具有适合调用者使用的生命周期。 ("ensuring" 可能构成记录与您的设计一起正常工作的客户端使用情况。)

通常 - 将代码典型地分为头文件和实现文件 - f(int) const 的实现甚至对调用代码都不可见,在这种情况下,编译器不知道 S 一个引用可能被 returned,也不知道那个 S 是否是临时的,所以它没有决定是否需要延长生命周期的基础。

除了显而易见的选项(例如,信任客户端编写安全代码,return按值或智能指针),值得了解一个更隐蔽的选项...

const S& f(int i) const & { ...; return *this; }
const S f(int i) const && { ...; return *this; }

函数体重载 f 之前的 &&& 如果 *this 是可移动的,则使用 && 版本,否则& 版本被使用。这样,有人将 const & 绑定到 f(...) 调用过期对象将绑定到对象的新副本,并根据本地 const 引用延长生命周期,而当对象还没有过期 const 引用将指向原始对象(只要引用仍然不能保证存在 - 需要一些谨慎)。