NRVO 是否也适用于协程?

Does NRVO also apply to coroutines?

本文关键字:适用于 是否 NRVO      更新时间:2023-10-16

在以下示例中,NRVO(命名返回值优化(按照本文应用:

std::string f1()
{
std::string str;
return str; // NVRO applies here!
}

但是,请考虑:

task<std::string> f2()
{
std::string str;
co_return str; // Does NVRO also apply here?
}
我知道

NRVO(命名返回值优化(自 C++17 年以来是强制性的:

不是。NRVO仍然是一种优化。

非命名的返回值优化 (RVO( 是强制性的。

// Is NVRO also guaranteed here?

不,因为NRVO永远无法保证。

为了完整起见,C++17 中的保证省略仅适用于直接从函数返回 prvalue。仅当编译器愿意时,返回命名变量才会受到省略的影响。

至于你问题的实质,co_return价值永远不会受到复制省略,保证或其他方式的影响。返回值的 Elision 键关闭return关键字,并且协程不允许使用return.他们使用co_return,标准中的省略逻辑不会关闭。所以省略不适用。

这样做的原因是协程的工作方式。协程是包含 promise 对象的函数。此 promise 对象是将协程的co_return值(和其他状态(引导到协程函数返回的"未来"对象的方式。

Elision 在普通函数中工作,因为调用约定要求调用方将返回值的存储传递给函数。因此,函数的实现可以选择仅在该存储中构建对象,而不是构建一个单独的堆栈对象并在return时复制到其中。

在协程中,返回值位于承诺中,因此这不会真正发生。

您链接的文章定义的 NRVO(即甚至不创建临时(对于协程来说不是一件事情,因为co_return的工作方式取决于用户提供的协程 promise 类型:co_return语句中的表达式被馈送到 promise 的return_value方法,该方法可以决定如何处理它。

但是,有一个相关的优化仍然可能有用。[class.copy.elision]/3 说:

隐式可移动实体是自动存储持续时间的变量,它是非易失性对象或对非易失性对象类型的右值引用。在以下复制初始化上下文中,在尝试复制操作之前,首先考虑移动操作:

  • 如果返回 ([stmt.return]( 或 co_return ([stmt.return.coroutine]( 语句中的表达式是一个(可能用括号括起来的(id 表达式,它命名在最里面的封闭函数或 lambda 表达式的主体或参数声明子句中声明的隐式可移动实体,或者
  • [...]

首先执行重载解析以选择要调用的副本或return_­value重载,就好像表达式或操作数是右值一样。如果第一个重载解析失败或未执行,则再次执行重载解析,将表达式或操作数视为左值。

这意味着,如果您从协程按名称返回局部变量,它将被移动,而不是复制(只要 promise 类型支持这一点(。例如,尽管无法复制std::unique_ptr<int>,但 clang 接受以下内容:

// Assume a coroutine task type called Task<T> whose associated promise has a
// return_value(T) method. The co_return here will successfully call that
// method.
Task<std::unique_ptr<int>> MakeInt() {
auto result = std::make_unique<int>(17);
co_return result;
}

因此,优化"即使未使用std::move值也作为右值引用提供给协程承诺"确实适用。但是标准并没有说"甚至没有调用移动构造函数",它不能,因为它取决于如何处理它给出的表达式的承诺。

只是因为答案看起来太长了。该标准规定,语句co_return <expr>;等效于:

P.return_value(<expr>);

其中P是协程的承诺对象。由此我想你可以回答这个问题和许多其他问题。

如果您正在寻找协程文档,请查看此处:

  • dcl.fct.def.coroutine
  • stmt.return.coroutine
  • expr.await
  • expr.yield
  • 支持.协程