【问题标题】:Functions returning a collection of objects in C++在 C++ 中返回对象集合的函数
【发布时间】:2013-02-21 14:05:45
【问题描述】:

在我当前的项目中,我需要实现相当多的函数/方法,这些函数/方法采用一些参数并生成一组结果(相当大)。所以为了在不复制的情况下返回这个集合,我可以创建一个新集合并返回一个智能指针:

boost::shared_ptr<std::vector<Stuff> > generate();

或者引用一个将被填充的向量:

void generate(std::vector<Stuff> &output);

这两种方法都有好处。第一个清楚地表明向量是函数的输出,在并行化场景中使用它是微不足道的,等等。第二个在循环中调用时可能更有效(因为我们不是每次都分配内存),但是那么参数就是输出就不是那么明显了,需要有人从向量中清理旧数据...

在现实生活中哪个更习惯(即最佳做法是什么)?在 C#/java 中我会争辩说第一个,在 C++ 中是什么情况?

另外,是否可以使用 C++11 按值有效地返回向量?会有什么陷阱?

【问题讨论】:

  • 请注意,如果您将可写引用传递给函数,那么很明显该函数将写入它并且它是一个输出。否则,它将是一个 const 引用。

标签: c++ design-patterns


【解决方案1】:

先做正确性,然后必要时优化

由于移动语义和返回值优化共同使普通函数结果不可复制,您可能必须努力使其效率低到足以值得优化工作

所以,只需将集合作为函数结果返回,如果您觉得太慢,则 MEASURE

【讨论】:

  • +10 用于过早优化。现代编译器对他们的能力感到惊讶。
【解决方案2】:

您应该按价值返回。

是否可以使用 C++11 按值有效地返回向量?

是的,C++11 支持move semantics。您返回一个值,但编译器知道它是一个临时值,因此可以调用一个特殊的构造函数(移动构造函数),该构造函数专门设计用于简单地“窃取”返回对象的内容。毕竟,你不会再使用那个临时对象了,既然可以移动它的内容,为什么还要复制它呢?

除此之外,可能值得一提的是,大多数 C++ 编译器,甚至是 C++11 之前的编译器,都实现了(Named) Return Value Optimization,这无论如何都会省略副本,不会产生任何开销。因此,您可能希望在优化之前实际衡量(可能)获得的性能损失。

我认为只有在需要reference semantics 时才应该通过引用传递或返回共享指针。这似乎不是你的情况。

【讨论】:

  • 你是说wtf omg(){ wtf it; return it; } 会调用move ctor吗?
  • @nurettin:如果有的话,是的。否则,它将调用复制 ctor。但很可能它不会调用它们,因为它会忽略复制/移动 (NRVO)。
  • 我无法重现您的断言on ideone 和带有各种 -O 标志的 g++ 4.7 和带有各种 -O 标志的 clang。移动 ctor 不会被调用。
  • @nurettin:你确实复制了它。编译器刚刚执行了 NRVO,所以它忽略了对移动构造函数的调用(否则你不太可能得到 42 打印)
  • @nurettin: p.s.: 要查看此内容,请尝试在命令行中添加 -fno-elide-constructors
【解决方案3】:

还有一种替代方法。如果你可以让你的函数模板化,让它们接受一个输出迭代器(它的类型是一个模板参数)作为参数:

tempalte<class OutputIterator>
void your_algorithm(OutputIterator out) {
    for(/*condition*/) {
        ++out = /* calculation */;
    }
}

这样做的好处是调用者可以决定他想在哪种集合中存储结果(输出迭代器可以例如直接写入文件,或者将结果存储在std::vector中,或者过滤它,等等)。

【讨论】:

    【解决方案4】:

    最佳实践可能会让您感到惊讶。我建议在 C++03 和 C++11 中按值返回。

    • 在 C++03 中,如果您创建一个 generate 本地的 std::vector 并将其返回,则编译器可能会忽略该副本(几乎肯定会)。参见 C++03 §12.8/15:

      在具有类返回类型的函数的return语句中,当表达式是与函数返回类型具有相同cv非限定类型的非易失性自动对象的名称时,可以通过构造省略复制操作自动对象直接转化为函数的返回值

    • 在 C++11 中,如果你创建一个局部于 generatestd::vector 并返回它,复制首先会被认为是一个移动(这已经非常快了)然后 那个 可能会被省略(几乎肯定会)。参见 C++11 §12.8/31:

      在具有类返回类型的函数的 return 语句中,当表达式是与函数具有相同 cv 非限定类型的非易失性自动对象(函数或 catch 子句参数除外)的名称时返回类型,直接在函数的返回值中构造自动对象即可省略复制/移动操作

      第 12.8/32 条:

      当满足或将满足省略复制操作的条件时,除了源对象是函数参数的事实,并且要复制的对象由左值指定时,重载决策选择构造函数首先执行复制,就好像对象是由右值指定的一样。

    所以按值返回!

    【讨论】:

      【解决方案5】:

      信不信由你,我将建议不要采用任何一种方法,而是采用明显的实现并按值返回!编译器通常能够优化掉将被诱导的名义副本,将其完全删除。通过以最明显的方式编写代码,您可以让未来的维护者非常清楚其意图是什么。

      但是假设您尝试按值返回并且您的程序运行太慢并且让我们进一步假设您的分析器显示按值返回实际上是您的瓶颈。在这种情况下,我将在堆上分配容器并返回为 C++03 中的 auto_ptr 或 C++11 中的 unique_ptr 以清楚地表明所有权正在转移并且生成没有保留以后复制那个shared_ptr 用于自己的目的。

      最后,http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/ 的系列文章为几乎完全相同的问题提供了一个很好的视角。

      【讨论】:

      • +1 用于引用进一步引用 clc++m 的博客;-)
      猜你喜欢
      • 1970-01-01
      • 2020-01-05
      • 1970-01-01
      • 2021-08-09
      • 1970-01-01
      • 2013-02-23
      • 1970-01-01
      • 2010-11-06
      • 2021-07-28
      相关资源
      最近更新 更多