【问题标题】:Can I rely on named return value optimisation for complicated return types?我可以依靠命名返回值优化复杂的返回类型吗?
【发布时间】:2018-01-29 12:39:20
【问题描述】:

考虑这样的事情:

typedef std::unordered_multiset<int> Set;
typedef std::set<Set> SetOfSets;

SetOfSets somethingRecursive(SomeType somethingToAnalyze) {
    Set s;
    // ...
    // check base cases, reduce somethingToAnalyze, fill in s
    // ...
    SetOfSets ss = somethingRecursive(somethingToAnalyze);
    ss.insert(s);
    return ss;
}

这种方法对于生成子集、排列等问题是相当标准的。但是,鉴于该类型的内部数据结构相当复杂(std::unordered_multiset 是哈希表和std::set'通常'是二叉搜索树),我只能希望编译器比我更聪明。

那么,谈论性能和(以防万一)C++14,我可以在这里返回SetOfSets,还是应该通过引用将其作为输出参数传递?

【问题讨论】:

  • 您使用的不是 RVO,而是 NRVO。区别是微妙的,但它就在那里 :) 人们经常把后者放在前者之下,所以是的。
  • @Rakete1111 NRVO 是 RVO 的一个子集。它有一个特殊的名称,主要是因为各种编译器首先为右值实现了 RVO,然后在 2003 年左右添加 NRVO 是一件大事。
  • 我需要查看 C++17 要求以获得完整答案。但是经典的 NRVO 并不关心类型有多“复杂”。一个典型的编译器基本上只是计算“唯一的返回语句是 return ss;ss 是一个本地非静态类类型变量,所以我不会将 ss 放在我自己的堆栈空间中,而是使用返回地址由调用者首先为其提供。”
  • @sigil:绝对不是。在 C++17 之前不能保证优化。虽然您可以通过使用调试器单步执行程序或将特殊消息放入构造函数来查看特定编译器正在执行的操作。
  • @YSC 因为从技术上讲,它不是复制省略。在 C++17 中,当返回纯右值时,没有临时物化。只是效果是“例如,或多或少地应用了 RVO”。请注意,这会产生后果 - 即使不存在复制或移动构造函数,您也可以在 C++17 中返回纯右值 (std::atomic)。

标签: c++ rvo nrvo


【解决方案1】:

在 C++17 之前,您根本不能依赖复制省略,因为它是可选的。但是,所有主流编译器很可能都会应用它(例如,GCC 即使使用 -O0 优化标志也会应用它,如果您愿意,您需要通过 -fno-elide-constructors 显式禁用复制省略)。

不过,std::set 支持移动语义,因此即使没有 NRVO,您的代码也可以。

请注意,在 C++17 中,NRVO 也是可选的。 RVO 是强制性的。


为了技术上正确,IMO,C++17 中没有 RVO,因为当返回纯右值时,没有临时物化可移动/复制。规则有点不同,但效果或多或少是一样的。或者,甚至更强大,因为在 C++17 中,复制/移动构造函数不需要按值返回纯右值:

#include <atomic>

std::atomic<int> f() {
  return std::atomic<int>{0};
}

int main() {
  std::atomic<int> i = f();
}

在 C++14 中,此代码无法编译。

【讨论】:

  • 根据en.cppreference.com/w/cpp/language/copy_elision 是这样的。但我没有检查标准,因为无法访问官方(非草案)版本。在最新草案中,请参见 15.8.3(1.1)。
  • @Bathsheba 一般情况下无法保证NRVO,因为一般情况下在对象构造时无法确定该对象是否会作为返回值。
  • 我不是语言律师,所以从技术上讲,我们可能不应该谈论 C++17 中的 RVO。来自 Cppreference:C++17 纯右值和临时值的核心语言规范与早期的 C++ 修订版根本不同:不再有临时复制/移动。描述 C++17 机制的另一种方式是“未实现的值传递”:prvalues 被返回和使用,而没有实现一个临时的。我使用这个术语的含义是它的效果。 (请注意,C++11 标准中也没有提到 RVO。)
  • @hvd IIRC,早期标准中已经存在限制,即您不能有多个返回不同对象的返回语句。问题是 NRVO 是否保证在这种情况下,无论如何,这是一个非常简单的创建-初始化-返回模式。
【解决方案2】:

不是真的。这确实很常见,但不能保证,所以这完全取决于您所需的置信水平。

AFAIK 此描述是最新的,涵盖了您的主题 copy_elision

关键语句是:“在以下情况下,允许但不要求编译器省略类对象的复制和移动构造,即使复制/移动构造函数和析构函数具有可观察到的副作用。"

然后是 NRVO 的描述(您提供)。并且,在下一个项目符号处有对 anonymous temporary RVO 的描述,并且项目符号表明这个 自 c++17 以来是强制性的。否则无法保证。

无论“复杂”是什么意思, 的复杂类型都无关紧要。这些优化基于变量的状态启动,例如:“一个无名的临时,未绑定到任何引用,将被复制或移动到相同类型的对象中(忽略顶级 cv 限定) em>”。请注意,这与变量类型的内部工作无关,而是与使用它的上下文有关。

你能做的就是做一个最小的例子,通过编译器分析汇编,看看优化是否启动,或者generate optimization report,然后看看你自己。


唯一的参考是明确声明允许优化对构造和销毁有副作用的类型。但这是作为例外的授权条款。对于没有副作用的类型,默认情况下已经可以使用。

【讨论】:

    【解决方案3】:

    那么,谈到性能和(如果重要的话)C++14,我可以在这里返回一个 SetOfSets 还是应该通过引用将它作为输出参数传递?

    如果你使用一个不错的编译器,你可以安全地按值返回它,因为会发生复制省略。但。但是不能保证复制省略1,并且您的编译器可能只有在给出正确的优化标志时才会这样做。

    [class.copy.elision]/1

    当满足某些条件时,允许实现省略类对象的复制/移动构造,即使为复制/移动操作选择了构造函数和/或为对象有副作用。

    这意味着,如果您的程序在不执行的情况下仍然有意义,您应该只依赖复制省略。


    1)constexpr 对象除外

    【讨论】:

      猜你喜欢
      • 2011-12-18
      • 1970-01-01
      • 1970-01-01
      • 2020-05-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多