【问题标题】:Is it good practice to fake an inserter?伪造插入器是一种好习惯吗?
【发布时间】:2023-03-06 03:15:01
【问题描述】:

我们被教导创建函数对象以使用算法。

有算法调用operator(),比如:

  • for_each
  • find_if
  • remove_if
  • 最大元素
  • count_if

这些函数对象通常应该从unary_functionbinary_function 继承,以表现得像函数谓词等。

但书籍通常不会展示创建OutputIterators 的示例:

例如遍历函数的输出,如 std::set_intersection(),我要提供一个目的容器, 然后遍历结果:

std::vector<int> tmp_dest;

std::set_difference (
        src1.begin(), src1.end(), 
        src2.begin(), src2.end(), 
        std::back_inserter(tmp_dest));

std::for_each( tmp_dest.begin(), tmp_dest.end(), do_something );
int res = std::accumulate( tmp_dest.begin(), tmp_dest.end(), 0 );

但认为有时使用每个算法的值而不先存储它们会更有效,例如:

std::set_difference (
        src1.begin(), src1.end(), 
        src2.begin(), src2.end(), 
        do_something );

Accumulator accumulate(0);  // inherits from std::insert_iterator ?
std::set_difference (
        src1.begin(), src1.end(), 
        src2.begin(), src2.end(), 
        accumulate );
  • 我们一般应该创建像这样的类 Accumulator 吗?
  • 它的设计应该是什么样的?
  • 它应该继承什么? Accumulator 可以继承自insert_iterator,但它并不是真正的迭代器(例如,它没有实现operator++()

被广泛接受的做法是什么?

【问题讨论】:

  • 我会说这很好,但不要从insert_iterator 继承,它不是 插入迭代器,它是消耗数据的输出迭代器。
  • 在算法列表中std::for_each() 与其他算法不同:其他算法使用谓词,std::for_each() 使用消费者。
  • @Dietmar:实际上,很多时候,find_if 被使用而不是 for_each,并且您将 谓词 用作 consumer,但是您还具有尽早中断迭代的优势。所以,真的,它们都是一样的。
  • @GrimFandango:虽然谓词可以像消费者一样使用,但实际上并不要求每次调用同一个副本。如果假设谓词是消费者,请确保对象的消费者部分具有引用语义。对于std::for_each(),需要移动函数对象(如果它是可移动的),即,该对象不会被复制并且可以直接作为消费者工作。我认为这些函数对象之间的。

标签: c++ algorithm stl


【解决方案1】:

如果您想要一个输出迭代器,它为收到的每个值调用您自己的函数,请使用Boost.Iterator's function_output_iterator

【讨论】:

    【解决方案2】:

    只要未来的维护者清楚代码的工作原理和作用,我认为这没有根本问题。

    我可能不会从任何标准类继承这样的操作(除了给它output_iterator_tag)。由于我们正在处理模板,因此我们不需要处理父接口。

    但请记住,您的声明 (eg it does not implement operator++() ) 似乎不正确:无论您作为“输出迭代器”传入的任何内容都需要满足输出迭代器的要求,包括可复制、取消引用分配、并且可以递增。您传入的任何对象类型都需要满足这些要求。

    【讨论】:

    • 我的意思是 如果我实现 operator++(),我可能会给未来的维护者留下错误的印象,这与调用者的观点无关(即只是一个遍历函数)。 Mark B,你能解释一下,没有有父接口的原因是什么?无论您是否继承,它都有效。我认为它展示了一种意图(即看起来像一个迭代器、一个 unary_function 等)。但这种继承是否还有其他用途?
    【解决方案3】:

    我对此的看法是使用 Boost(也显示了 set_difference 的 Boost Range 算法版本,虽然离题了):

    #include <set>
    #include <boost/range/algorithm.hpp>
    #include <boost/function_output_iterator.hpp>
    #include <cassert>
    
    void do_something(int) {}
    
    int main()
    {
        const std::set<int> 
             src1 { 1,2,3 }, 
             src2 { 1,9 };
    
        unsigned total = 0;
    
        boost::set_difference(src1, src2, 
                    boost::make_function_output_iterator([&](int i) 
                    { 
                        total += i*i; 
                    }));
    
        assert(total == 13); // 2*2 + 3*3
    }
    

    Live On Coliru

    【讨论】:

    • 整洁!为什么这样的东西不成为标准?
    • @GrimFandango还没有。范围建议正在审议中。你可以在 iso-cpp.org 添加你的声音:/我相信某种范围概念可能会出现在 c++1y 中——但很难预测什么是“风味”(所有竞争的想法都有其缺点,而'jells' 最好的 c++1y 可能会赢)
    【解决方案4】:

    采用输出迭代器的算法的目标是由输出迭代器表示的一系列值。他们使用迭代器有两个原因:

    1. 结果很可能存储在其他地方,即迭代器很有用。
    2. 协议要求每个位置只写入一次。这比函数调用接口更具限制性,即有额外的保证。

    对于某些算法,提供了两种版本,一种具有函数调用接口,另一种具有迭代器接口。例如,这就是std::for_each()std::copy() 之间的区别。

    在任何情况下,如果您只需要在需要输出迭代器的地方调用一个函数,只需将其他迭代器操作设为无操作并在分配给*it 的结果时调用该函数:这将创建一个完美的输出迭代器。

    【讨论】:

      【解决方案5】:

      以下作品:

      #include <cassert>
      #include <algorithm>
      
      class AccumulatorIterator
      {
      public:
          explicit AccumulatorIterator(int initial) : value(initial) {}
      
          AccumulatorIterator& operator = (int rhs) { value += rhs; return *this; }
          AccumulatorIterator& operator *() { return *this; }
      
          AccumulatorIterator& operator ++() { return *this; }
          operator int() const { return value; }
      private:
          int value;
      };
      
      int main() {
          int first[] = {5,10,15,20,25};
          int second[] = {50,40,30,20,10};
      
          std::sort(std::begin(first), std::end(first));   //  5 10 15 20 25
          std::sort(std::begin(second), std::end(second)); // 10 20 30 40 50
      
          const int res = std::set_intersection (std::begin(first), std::end(first),
              std::begin(second), std::end(second), AccumulatorIterator(0));
      
          assert(res == 10 + 20);
          return 0;
      }
      

      【讨论】:

      • 谢谢。这个 cast operator 引起了我的注意。这很有帮助,但它不是有点脆弱吗?我的意思是,如果调用者没有将结果存储到int,它可能无法按预期工作。 int get_value() { return value; } 不是更安全,因为它更明确吗?
      • 由于函子是按值取值的,你不能在std::set_intersection的最后一个参数上使用get_value()(除非你使用某种shared_ptr&lt;int&gt;)。您可以在std::set_intersection 返回的AccumulatorIterator 对象中使用get_value()。我主要将AccumulatorIterator 视为函数的助手。这个类本身看起来很奇怪。
      猜你喜欢
      • 2014-11-05
      • 1970-01-01
      • 1970-01-01
      • 2018-07-29
      • 2019-02-01
      • 2021-04-11
      • 2011-07-17
      • 2019-03-06
      • 2017-12-09
      相关资源
      最近更新 更多