"end ()" итератор для обратных вставок? - PullRequest
5 голосов
/ 01 января 2011

Для итераторов, таких как те, которые возвращаются из std::back_inserter(), есть ли что-то, что можно использовать в качестве "конечного" итератора?

Сначала это кажется немного бессмысленным, но у меня есть API, который:

template<typename InputIterator, typename OutputIterator>
void foo(
    InputIterator input_begin,
    InputIterator input_end,
    OutputIterator output_begin,
    OutputIterator output_end
);

foo выполняет некоторую операцию над входной последовательностью, генерируя выходную последовательность.(Длина кого известна как foo, но может или не может быть равна длине входной последовательности.)

Взятие параметра output_end является нечетной частью: std::copy не делает этого, например, и предполагает, что вы не собираетесь передавать это мусор.foo делает это для проверки диапазона: если вы передаете слишком маленький диапазон, он генерирует исключение во имя защитного программирования.(Вместо того, чтобы потенциально перезаписывать случайные биты в памяти.)

Теперь, скажем, я хочу передать foo задний вставщик, в частности, один из std::vector, который не имеет ограничений вне ограничений памяти.Мне все еще нужен «конечный» итератор - в этом случае то, что никогда не сравнится с равным.(Или, если бы у меня был std::vector, но с ограничением по длине, возможно, он иногда сравнивался бы равным?)

Как мне поступить так?У меня есть возможность изменить API foo - лучше не проверять диапазон, а вместо этого предоставлять альтернативные средства для получения требуемого диапазона вывода?(Что в любом случае понадобится для необработанных массивов, но не требуется для обратных вставок в вектор.) Это может показаться менее надежным, но я изо всех сил пытаюсь заставить работать «надежный» (см. Выше).

Ответы [ 3 ]

5 голосов
/ 01 января 2011

Если foo проверяет, достаточно ли distance(output_begin, output_end) для размещения результатов, что вы могли бы использовать в качестве "конечного" итератора?A back_inserter добавляет элементы в конец;distance между местом, в котором back_inserter добавляет элементы, и концом последовательности, по определению, 0.

A foo с std::copy -подобной подписью foo(InIt, InIt, OutIt) это, на мой взгляд, ваш лучший вариант.Это на самом деле не "не надежно".Для большинства алгоритмов этот вид проверки диапазона требуется только в сборках отладки по соображениям производительности, и приличная реализация Стандартной библиотеки (например, Стандартная библиотека Visual C ++) уже обеспечит большую проверку диапазона в сборках отладки.

В качестве альтернативы, вы можете создать back_inserting_foo(InIt, InIt, Container), хотя создание особого случая для этого будет немного необычным и возлагает большую нагрузку на пользователей функции, чтобы узнать, какую перегрузку им нужно использовать для различных типов итераторов.

3 голосов
/ 01 января 2011

Вы можете избежать изменения API foo(), выполняя проверку безопасности на ходу, проверяя, что curr_output_iter != output_end перед записью каждого элемента (см. Ниже), а не один раз при запуске с distance() отметьте, как Джеймс МакНеллис предлагает .

Для этого потребуется написать собственный «адаптер итератора», чтобы обеспечить «расширенный» выходной итератор, который может проверить, находится ли он в конце своего допустимого диапазона. Тогда вы по праву изменили бы типовые имена шаблонов с OutputIterator на SafeOutputIterator - даже если это всего лишь документация, потому что это не может быть применено в C ++. Мы различаем «ограниченные» и «неограниченные» пары итераторов: для последних два итератора никогда не сравнятся равными, и фактически второй итератор никогда не понадобится ни для чего, кроме его типа.

/* Metafunction for determining whether the range has a fixed endpoint.
 * Assume all iterators are bounded except for OutputIterators. */
template <typename Tag>
struct helper {
    static bool value = true;
};

template <>
struct helper<output_iterator_tag> {
    static bool value = false;
};

template <typename It>
struct is_bounded {
    static bool value = helper<typename iterator_traits<It>::iterator_category>::value;
};

/* Wraps an output iterator. */
template<typename It, bool BOUNDED = is_bounded<It>::value>
class safe_output_iterator {
    typedef typename iterator_traits<It>::value_type value_type;

public:
    explicit safe_output_iterator(It iter, size_t limit = 0) : iter_(iter), limit_(limit) {}

    safe_output_iterator& operator++() {  /* Preinc */
        ++iter_;
        return *this;
    }

    safe_output_iterator operator++(int) {  /* Postinc */
        safe_output_iterator temp(*this);
        ++iter_;
        return temp;
    }

    /* Trick: Deferencing returns the same object, so that operator=() works */
    safe_output_iterator& operator*() {
        return *this;
    }

    /* Will be called despite "dereferencing" this iterator object */
    safe_output_iterator& operator=() {
        /* Insert check for limit == 0 here if you want */
        --limit_;
        return *_iter;
    }

    /* Addition to the OutputIterator concept. */
    bool operator==(safe_output_iterator const& x) {
        return BOUNDED && limit_ == x.limit_;
    }

    bool operator!=(safe_output_iterator const& x) {
        return !(*this == x);
    }

private:
    It iter_;
    size_t limit_;
};

/* Helper function templates to avoid typing out long typenames */

/* Handle iterators */
template <typename It>
safe_output_iterator<It> soi_begin(It it, size_t limit = 0) {
    return safe_output_iterator<It>(it, limit);
}

template <typename It>
safe_output_iterator<It> soi_end(It it, size_t limit = 0) {
    return safe_output_iterator<It>(it, limit);
}

/* Handle STL containers like vector and list */
template <typename C>
safe_output_iterator<It> soi_begin_cont(C cont) {
    return safe_output_iterator<typename C::iterator>(cont.begin(), cont.size());
}

template <typename C>
safe_output_iterator<It> soi_end_cont(C cont) {
    /* Actually the iterator supplied (here cont.end()) is unimportant... */
    return safe_output_iterator<typename C::iterator>(cont.end());
}

Теперь вы можете написать код, например:

vector<int> u(10, 42), v(ENOUGH_SPACE), w, x(ENOUGH_SPACE);

// Explicit iterator pair; bounded
foo(u.begin(), u.end(), soi_begin(v.begin(), ENOUGH_SPACE), soi_end(v));

// Explicit iterator pair; unbounded (2nd iterator ignored, but we need the type)
foo(u.begin(), u.end(), soi_begin(w.back_inserter()), soi_end(w.back_inserter()));

// Use whole container
foo(u.begin(), u.end(), soi_begin_cont(x), soi_end_cont(x));

Вы можете либо foo() проверить curr_output_iter != output_end перед каждой записью через *curr_output_iter, либо в качестве альтернативы вставить чек в safe_output_iterator::operator=(). Последнее кажется предпочтительным, поскольку вы не можете забыть выполнить проверку всякий раз, когда пара safe_output_iterator s передается любому произвольному алгоритму (предположительно, это похоже на то, как работают отладочные версии STL). Вы также можете написать перегрузки soi_begin_cont() и soi_end_cont() для массивов фиксированного размера.

Все это было бы гораздо менее громоздким, если бы диапазоны использовались алгоритмами STL вместо пар итераторов - таким образом, один шаблон функции мог бы возвращать соответствующий диапазон, охватывающий, например, весь массив.

1 голос
/ 03 января 2011

Как я уже упоминал в комментарии к ответу j_random_hacker, если вы модифицируете алгоритм так, что передаваемые ему итераторы могут быть разных типов, например,

template <typename InIt1, InIt2, OutIt1, OutIt2>
void foo(InIt1 in_begin, InIt2 in_end, OutIt1 out_begin, OutIt2 out_end) { }

тогда вы можете очень легко написать класс back_inserter_end для проверки:

class back_inserter_end 
    : std::iterator<std::output_iterator_tag, void>
{ };

bool operator==(back_inserter_end, back_inserter_end) { return true;  }
bool operator!=(back_inserter_end, back_inserter_end) { return false; }

template <typename Container>
bool operator==(const back_insert_iterator<Container>&, back_inserter_end) { 
     return false; 
}

template <typename Container>
bool operator==(back_inserter_end, const back_insert_iterator<Container>&) { 
    return false; 
}

template <typename Container>
bool operator!=(const back_insert_iterator<Container>&, back_inserter_end) { 
    return true; 
}

template <typename Container>
bool operator!=(back_inserter_end, const back_insert_iterator<Container>&) { 
    return true; 
}

Затем вы можете вызвать свой «безопасный» алгоритм:

foo(it, it, std::back_inserter(v), back_inserter_end());

Внутри вашего "безопасного" алгоритма вы можете проверить, является ли out_it == out_end перед использованием out_it; поскольку out_it является back_insert_iterator, этот тест всегда будет возвращать false (что является желаемым поведением).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...