【问题标题】:Copy elision, std::move, and chained function calls复制省略、std::move 和链式函数调用
【发布时间】:2018-08-01 20:26:59
【问题描述】:

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

对于初学者,我理解NRVO出现在下面的例子中,返回值是直接在destination变量中构造的:

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

Type a = MakeType();

但是,假设我们有另一个函数,它以 Type 作为参数:

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,它甚至会损害性能
  • stackoverflow.com/a/9595610/3783662 这个答案很好地解释了它。对于您的示例,您可以做的最好的事情是在函数中,将输入参数移动到新的本地对象中。这是一个动作。其余的将是 nrvo 和复制省略
  • Type complex_type = param_type; 不是复制省略上下文,std::move(MakeType()) 是一种悲观化,因为MakeType() 是一个prvalue
  • 没有办法让 MakeType()t 中构造为您上次调用中的语法

标签: c++ optimization move-semantics copy-elision


【解决方案1】:

复制省略是编译器用来防止不需要的复制的技术。基本上,它在函数外部预先分配内存并将其传递给使用。如果您是临时的,它将在堆栈上。

将 std::move 添加到返回类型没有帮助。您已经按值返回,因此您已经有一个右值。如果我没有操作,使用 std::move 将其转换为没有右值。 但是,我不知道细节,在某些情况下添加它会损害性能。

专注于 2: 将 std::move 添加到函数调用仅在由非常量引用返回时才会产生副作用。在这些情况下,您很可能编写了一个错误,因为原始文件将被移走。

对于 3 号: 我最喜欢的是使用f(Arg &&a),因为这要求所有调用者都传递右值。如果性能不太重要,例如:您在分析中没有找到它。值参数(一些调用者可以复制)甚至 const-reference 都可以(函数不能触及参数,所以应该复制)。

正如 cmets 所指出的,函数的实现也应该写为auto result = std::move(a),因为您的参数不能从 NRVO 中受益。

Clang 的最新版本对何时应使用 std::move 以及何时删除它有很好的警告。我建议启用它们。 GCC 可能有一些类似的警告,但我不是最新的。

简而言之:您的原始代码是使用和信任您的编译器的最佳版本,如果它对此有警告。

【讨论】:

  • “简而言之:您的原始代码是最适合使用的版本”。我认为这不是真的。如 cmets 中所述,在函数内部移动将用移动替换一个副本。示例:coliru.stacked-crooked.com/a/a2937e370af4da86
  • 问题是如何调用函数,而不是函数impl。
  • 哦。嗯。通话方没问题。 OP 的问题 2:“如果不是,我们可以在 std::move(MakeType()) 之类的函数调用本身上策略性地使用 std::move,也许,以避免不必要的复制吗?”所以我只是看着一切。
  • 简短的回答:没有任何效果。让我更新我的帖子
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-10-26
  • 2015-03-09
  • 1970-01-01
  • 2013-12-08
  • 1970-01-01
  • 1970-01-01
  • 2015-03-21
相关资源
最近更新 更多