复制elision、std::move和链式函数调用

Copy elision, std::move, and chained function calls

本文关键字:函数调用 move std 复制 elision      更新时间:2023-10-16

我一直在研究复制省略在没有直接分配给左值并且可能被链接或在以后使用时的行为,但没有找到任何具体的答案。

首先,我理解NRVO出现在以下示例中,返回值直接构建在目标变量中:

Type MakeType() {
Type type;
// ...
return type;
}
Type a = MakeType();

然而,假设我们有另一个函数,它将类型作为参数:

Type MakeComplexType(/*some signature*/ param_type) {
Type complex_type = param_type
// ...
return complex_type;
}

我们称之为:

Type t = MakeComplexType(MakeType());
  1. 是否可以将省略复制一直链到t
  2. 如果没有,我们是否可以战略性地使用std::move,也许是在像std::move(MakeType())这样的函数调用本身上,以避免不必要的复制
  3. param_type的签名应该是什么,使得上面对t的分配是最有效的

拷贝省略是编译器用来防止不需要的拷贝的技术。基本上,它在函数之外预先分配内存,并将其传入以供使用。如果是临时的,它将在堆栈上。

将std::move添加到返回类型没有帮助。您已经按值返回了,所以您已经有了一个右值。用std:"移动应该是一个no-op吗?"。然而,我不知道细节,在某些情况下,添加它可能会影响性能。

重点关注2:在函数调用中添加std::move只会在非常量引用返回时产生副作用。在这些情况下,您很可能编写了一个bug,因为原来的bug将被移走。

对于编号3:我最喜欢使用f(Arg &&a),因为这需要所有调用方传递右值。如果性能不那么重要,例如:您在评测中没有发现它。值参数(某些调用方可以复制(甚至常量引用都可以(函数不能接触参数,所以应该复制(。

如注释所示,函数的实现也应该编写auto result = std::move(a),因为您的参数没有从NRVO中受益。

Clang的最新版本对什么时候应该使用std::move以及什么时候删除它有很好的警告。我建议启用它们。GCC可能会有一些类似的警告,但我还没有跟上

简而言之:您的原始代码是最好的版本,如果编译器对此有警告,请相信它。