Хорошая практика - подделывать вставку?

Нам учат создавать функциональные объекты для использования алгоритмов.

Существуют алгоритмы, которые вызывают operator(), например:

  • for_each
  • find_if
  • remove_if
  • max_element
  • count_if

Эти объекты функций обычно должны наследовать от unary_function или binary_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 );
  • Должны ли мы вообще создавать такие классы, как этот Аккумулятор?
  • Каким должен быть его дизайн?
  • На что он должен унаследовать? Аккумулятор может наследовать от insert_iterator, но он не является итератором (например, он не реализует operator++())

Каковы общепринятые практики?

Ответы

Ответ 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;
}

Ответ 2

Если вы хотите, чтобы итератор вывода вызывал вашу собственную функцию для каждого полученного значения, используйте Boost.Iterator function_output_iterator.

Ответ 3

Я не вижу фундаментальной проблемы с этим, если он ясно объясняет будущим сопровождающим, как работают коды и что они делают.

Я бы, вероятно, не наследовал такую ​​операцию из любого стандартного класса (кроме предоставления ей output_iterator_tag). Поскольку мы имеем дело с шаблонами, нам не нужно иметь родительский интерфейс.

Но имейте в виду, что ваше утверждение (eg it does not implement operator++() ) не кажется правильным: что бы вы ни проходили, поскольку "выходной итератор" должен удовлетворять требованиям выходных итераторов, которые включают в себя возможность копирования, разыменования, и возрастает. Независимо от типа объекта, который вы проходите, необходимо выполнить эти требования.

Ответ 4

Мое занятие будет использовать 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
}

Смотрите Live On Coliru

Ответ 5

Целью алгоритмов, принимающих выходной итератор, является последовательность значений, представленных выходным итератором. Они используют итераторы по двум причинам:

  • Весьма вероятно, что результат хранится где-то в другом месте, т.е. полезен итератор.
  • Протокол предусматривает, что каждая позиция записывается только один раз. Это более отзывчиво, чем интерфейс вызова функции, т.е. Есть дополнительная гарантия.

Для некоторых алгоритмов предусмотрены обе версии, одна с интерфейсом вызова функций и одна с интерфейсом итератора. Например, это разница между std::for_each() и std::copy().

В любом случае, если все, что вам нужно, имеет функцию, называемую, где требуется итератор вывода, просто нужно, чтобы другие операции итератора были no-ops и вызывали функцию при назначении на результат *it: это создает идеальный выходной итератор вывода.