Хорошая практика - подделывать вставку?
Нам учат создавать функциональные объекты для использования алгоритмов.
Существуют алгоритмы, которые вызывают 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
: это создает идеальный выходной итератор вывода.