【问题标题】:Why `copy_n`, `fill_n` and `generate_n`?为什么是`copy_n`、`fill_n`和`generate_n`?
【发布时间】:2014-03-13 15:09:05
【问题描述】:

为什么在 C++11 中提供了 copyfillgenerate_n 版本,为什么只提供这些算法?

【问题讨论】:

  • 看起来他们已经在 SGI STL 中,这里是包含他们的建议:n2569
  • copy_nfill_n 替换了 C 标准库中的 memcpymemset 的功能,为人们从 C 迁移到 C++ 提供了一个很好的垫脚石。跨度>
  • 如果迭代器不是随机访问的,获取结束迭代器可能需要O(n)时间。一些迭代器在推进时也有副作用。所以std::copy_n 的某些用途不能由std::copy 实现。
  • @C.R.为什么这是特定于copyfillgenerate
  • @Vincent 不是,但它被认为是最有必要的。不要问我为什么。

标签: algorithm c++11 stl standards stl-algorithm


【解决方案1】:

一般来说,STL 只提供原语,人们可以从中定义合适的变体。

SGI 文档为提供您指出的例外情况提供了以下理由:

  • copy_n 适用于不是前向迭代器的输入迭代器。

  • fill_ngenerate_n 适用于不是正向迭代器的输出迭代器。

正如@Jared Hoberock 在 cmets 中指出的那样,<memory>header 还具有 uninitialized_ 版本的 copy_nfill_n,它们是已知计数时的优化版本。

C++11 提供了一些其他便利的包装器(例如 find_if_not),但是有了 lambda 谓词,这样的包装器变得更容易自己编写。

注意:还有一个search_n,但这与search 的语义不同,因为后者将查看两个输入范围之间的重叠,而前者将查看单个输入范围中的连续元素输入范围。

【讨论】:

    【解决方案2】:

    我们以std::generate()std::generate_n() 为例。前者采用ForwardIterators,指向范围的开始和结束,后者是OutputIterator。这具有微妙的含义,例如:

    #include <algorithm>
    #include <vector>
    
    int main() {
    
      std::vector<int> v;
    
      v.resize(5); // <-- Elements constructed!!!
    
      std::generate(v.begin(), v.end(), [](){ return 42; });
    
      std::vector<int> w;
    
      w.reserve(5); // Space only reserved but not initialized
    
      std::generate_n(std::back_inserter(w), 5, [](){ return 42; });
    
    }
    

    这足以让我证明这两个版本的存在是正确的。

    您说得对,在许多用例中,这些功能的功能重叠,其中一个可能看起来多余。

    为什么只有这些算法?

    可能是因为还没有人为其他算法提出_n 版本。正如TemplateRex 链接的那样,也可能有std::iota_n()What would be a good implementation of iota_n (missing algorithm from the STL)?

    【讨论】:

      【解决方案3】:

      Alexander Stepanov(STL 的原始设计师)在他的优秀视频系列Efficient Programming with Components 中讨论了这个问题(以及许多其他问题)。他最初提出了许多其他_n 个 STL 算法的变体,但当 STL 最初标准化时,它们并没有被接受。有些是为 C++11 重新添加的,但仍有一些他认为应该可用但缺失了。

      _n 种算法变体之所以有用有很多原因。您可能有一个输入迭代器或输出迭代器,您知道它们可以产生或消耗 n 个元素,但您没有办法获得合适的结束迭代器。您可能有一个容器类型,例如您知道对于操作来说足够大的列表,但它并没有为您提供一种有效的方法来获取超出您的开始迭代器的 n 个位置的迭代器。您可能有一个像 binary_search / lower_bound 这样的算法,它最自然地以计数范围表示。当您已经有 n 但没有结束迭代器并且必须生成一个来调用算法的非 _n 变体时,这可能会更方便。

      【讨论】:

      • +1,但我觉得更简洁(如果更冗长)的解决方案是提供一个迭代器适配器模板,该模板采用不需要符合 ForwardIterator 的某些基本迭代器类型的迭代器(例如一个普通的 OutputIterator) 和一个计数,并返回一对包装的 ForwardIterators,然后可以将其传递给普通的 copy() 等函数。迭代器适配器将简单地记录 ++ 被调用的次数,当达到构造时传入的计数时,它的 operator==() 将返回 true
      • 不幸的是,这种方法效率也较低。 Eric Niebler 在他最近关于范围概念的系列博文中谈到了一些原因:ericniebler.com/2014/02/16/delimited-ranges
      • 有趣的链接,谢谢!这会减慢速度是有道理的,尤其是当元素很小并​​且复制/生成代码微不足道时。不过,我建议您查看 Fuz 在该页面上的评论——他/她展示了允许开始和结束迭代器是不同的 types 是如何实现一种非常自然的方式来处理哨兵而没有任何抽象惩罚的!
      • 在该系列的后面部分,Eric 继续准确地提出了这个想法(开始和结束迭代器是不同的类型),他称之为可迭代对象。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-06-11
      • 1970-01-01
      • 2011-07-01
      相关资源
      最近更新 更多