伪造插入器是一个好的做法吗?

16

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

有些算法会调用operator(),例如:

  • for_each
  • find_if
  • remove_if
  • max_element
  • 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++())。

有哪些被广泛接受的做法?


3
我认为这样做没问题,但不要继承自insert_iterator,它不是插入迭代器,而是一个消耗数据的输出迭代器。 - David Rodríguez - dribeas
在算法列表中,std::for_each()与其他算法不同:虽然其他算法使用谓词,但std::for_each()使用消费者。 - Dietmar Kühl
@Dietmar:实际上,很多时候会使用find_if而不是for_each,并且您将谓词用作消费者,但您也有提前中断迭代的优势。因此,它们实际上都是相同的。 - Grim Fandango
@GrimFandango:虽然谓词可以像消费者一样使用,但实际上并没有要求每次调用都是相同的副本。如果您假设谓词是一个消费者,请确保对象的消费者部分具有引用语义。对于std::for_each(),有一个要求,即函数对象被移动(如果它是可移动的),即该对象不会被复制,并且可以直接作为消费者工作。我认为这些函数对象之间存在差异。 - Dietmar Kühl
5个回答

6

4
只要未来的维护者清楚代码是如何工作以及它在做什么,我认为这个并没有根本性的问题。
我可能不会从任何标准类(除了给它output_iterator_tag)继承这样的操作。因为我们处理的是模板,所以不需要有父级接口来处理。
但请记住,你的语句(例如,它没有实现operator++())似乎是不正确的:无论你传入什么作为“输出迭代器”,它都需要满足输出迭代器的要求,包括可复制、解引用即赋值、可增量。无论你传入的对象类型需要满足这些要求。

我的意思是,如果我实现operator ++(),它与调用者的角度无关(只是一个遍历函数),那么我可能会给未来的维护人员留下错误的印象。马克B,请问一下,为什么不设置父接口呢?它可以适用于继承和非继承情况下。我认为这表明了一种意图(即看起来像迭代器、一元函数等)。但这种继承是否适用于其他目的呢? - Grim Fandango

3

我认为可以使用Boost(还展示了Boost Range算法版本的set_difference,虽然有些离题):

#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
}

点击此链接可在线查看运行效果。


太好了!为什么这样的东西没有被纳入标准呢? - Grim Fandango
@GrimFandango 目前还没有。范围提案正在进行中。您可以在 iso-cpp.org 上发表意见:/ 我认为某种范围概念可能会进入 c++1y - 但很难预测什么“风味”(所有竞争的想法都有其缺点,与 c++1y 的其他部分最契合的那个可能会获胜)。 - sehe

2
算法使用输出迭代器的目标是由输出迭代器表示的值序列。它们使用迭代器有两个原因:
1. 结果很可能存储在其他地方,即迭代器非常有用。 2. 协议规定每个位置只写一次。这比函数调用接口更加严格,即提供了额外的保证。
对于某些算法,提供了具有函数调用接口和迭代器接口的两个版本。例如,这就是 `std::for_each()` 和 `std::copy()` 之间的区别。
无论如何,如果您所需的仅是需要输出迭代器的函数调用,请将其他迭代器操作设置为 no-ops,并在分配给 `*it` 的结果时调用该函数:这将创建一个完全有效的输出迭代器。

1
以下工作:
#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;
}

谢谢。这个转换运算符引起了我的注意。它很有用,但是它不是有点脆弱吗?我的意思是,如果调用者没有将结果存储到int中,它可能无法按预期工作。一个int get_value() { return value; }更加明确,更安全,对吧? - Grim Fandango
由于函数对象是按值传递的,因此您不能在std::set_intersection的最后一个参数上使用get_value()(除非您使用某种形式的shared_ptr<int>)。您可以在std::set_intersection返回的AccumulatorIterator对象中使用get_value()。我大多数情况下将AccumulatorIterator视为函数的助手。该类本身似乎很奇怪。 - Jarod42

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接