Как убедиться, что итераторы не выходят за пределы end ()? - PullRequest
11 голосов
/ 04 октября 2010

Я использовал advance на некоторых iterators, но я боюсь возможного скачка выше end().Я хотел бы убедиться, что мои итераторы остаются между границами, я подумал о distance, но, похоже, он не возвращает то, что я ожидал (неположительные значения, когда итераторы превышают end()).Как бы вы убедились, что нет чехарды?

#include <iostream>
#include <iterator>
#include <list>
using namespace std;

int main () {
  list<int> mylist;
  for (int i=0; i<10; i++) mylist.push_back (i*10);

  list<int>::const_iterator first = mylist.begin();
  const list<int>::const_iterator last = mylist.end();

  cout << "The distance is: " << distance(first,last) << endl; // 10
  advance(first, 10);
  cout << "The distance is: " << distance(first,last) << endl; // 0
  advance(first, 1);
  cout << "The distance is: " << distance(first,last) << endl; // 10
  advance(first, 10);
  cout << "The distance is: " << distance(first,last) << endl; // 0

  return 0;
}

Вот вывод:

The distance is: 10
The distance is: 0
The distance is: 10
The distance is: 0

Ответы [ 6 ]

14 голосов
/ 04 октября 2010

advance () past end () приводит к неопределенному поведению.Вам нужно будет протестировать этот фрагмент:

  template <class Iter, class Incr>
  void safe_advance(Iter& curr, const Iter& end, Incr n)
  {
    size_t remaining(std::distance(curr, end));
    if (remaining < n)
    {
      n = remaining;
    }
    std::advance(curr, n);
  }

Вам нужно подумать о том, что произойдет, если вы не сдвинете полную сумму (curr возвращается как end().

8 голосов
/ 04 октября 2010

Неэффективно вызывать distance или advance на чем-либо, кроме моделей RandomAccessIterator: вызовы O (n), где n представляет расстояние.

Кроме того, list на самом деле не является моделью Sequence в том смысле, что его метод size не гарантированно является константой (или даже амортизированной константой), в действительности он вполне может быть O (n).

Глядя на код (и если вы не можете использовать ничего, кроме list), есть две возможности:

  • Не используйте заранее, перемещайте по одному предмету за раз и проверяйте end на каждом шаге.
  • Сначала вычислите размер раз и навсегда, затем вы знаете, сколько вы можете продвинуть, прежде чем вызывать неопределенное поведение (вы можете оставить счетчик на стороне, обернуть итератор и т. Д.) *

Давайте действовать на это:

// Advance one at a time
// replace calls to advance by this function

typedef list<int>::const_iterator const_iterator;

const_iterator safe_advance(const_iterator it, const_iterator end, size_t n)
{
  for (size_t i = 0; i != n && it != end; ++i) { ++it; }
  return it;
}


// Precompute size
size_t const size = list.size();

size_t remaining = size;
const_iterator begin = list.begin();

size_t ad = std::min(10, remaining);
advance(begin, ad);
remaining -= ad;

ad = std::min(1, remaining);
advance(begin, ad);
remaining -= ad;

Хотя вести этот подсчет утомительно ...

EDIT:

Решение законных задач Дэвида по обобщению решений:

// Advance `it` from n, or up to end
// returns the number of steps that could not be performed
template <typename Iterator>
size_t safe_advance(Iterator& it, Iterator const& end, size_t n)
{
  size_t i = 0;
  for (; i != n && it != end; ++i, ++it);
  return n - i;
}

Обратите внимание, что для двунаправленных итераторов, advance может использоваться с отрицательными расстояниями, однако это потребует также ввода begin и станет очень утомительным. Поэтому я предпочитаю второй метод:

template <typename BI>
size_t safe_reverse(BI& it, BI const& begin, size_t n)
{
  for (; n != 0 && it != begin; --n, --it);
  return n;
}

И, наконец, хотя я здесь не буду этого делать, было бы хорошо специализировать шаблоны для RandomAccessIterator, поскольку эти операции можно выполнять в O (1) с такими.

4 голосов
/ 04 октября 2010

Расстояние не может этого сделать.Когда ваш контейнер не предлагает произвольный доступ, он пытается достичь конца путем опережающего старта, что приведет к хаосу, когда старт запустился в последний раз.достичь последнего) и проверять расстояние перед каждым продвижением и продвигаться только на X <= (расстояние (первое, последнее), чтобы не обогнать последнего. </p>

3 голосов
/ 04 октября 2010

Это просто. Чтобы избежать выхода за пределы .end(), просто избегайте выхода за пределы .end(). Ситуация ничем не отличается, если не использовать указатель NULL и т. Д. Структурируйте свой код так, чтобы этого никогда не происходило. Например. используйте циклы, которые используют такие условия, как it != v.end() и т. д. Некоторые популярные реализации стандартных библиотек обнаруживают эти ошибки и сообщают вам, но не следует полагаться на них, используйте их только во время тестирования / отладки.

UPDATE

Если вы не можете быть уверены, что вы можете advance() на сумму больше единицы, то вы просто не должны advance() более чем на единицу.

2 голосов
/ 05 октября 2010

Во-первых, вы также можете написать обертку итератора, которая хранит конечный итератор и проверяет критические операции.

Например, для краткости используется значение iterator_facade для boost, а не для проверки недостаточности.

#include <boost/iterator/iterator_facade.hpp>
#include <iterator>
#include <stdexcept>

template <class Iter>
class checked_iterator:
    public boost::iterator_facade<
        checked_iterator<Iter>,
        typename std::iterator_traits<Iter>::value_type,
        typename std::iterator_traits<Iter>::iterator_category
    >
{
    Iter it, end;
public:
    checked_iterator(Iter it, Iter end): it(it), end(end) {} 
    void increment() 
    { 
        if (it == end) { throw std::range_error("checked_iterator: increment beyond end"); }
        ++it;
    }
    void decrement()
    {
        //TODO: this is not checked
        --it;
    }
    bool equal(const checked_iterator& other) const
    {
        return it == other.it;
    }
    typename std::iterator_traits<Iter>::reference dereference() const 
    {
        if (it == end) { throw std::range_error("checked_iterator: dereference end"); }
        return *it;
    }
    void advance(typename std::iterator_traits<Iter>::difference_type n)
    {
        //TODO: not checked for underflow
        if (std::distance(it, end) < n) { throw std::range_error("checked_iterator: advance beyond end"); }
        it += n;
    }
    typename std::iterator_traits<Iter>::difference_type distance_to(const checked_iterator& other) const
    {
        return other.it - it;
    }
    Iter base() const { return it; }
};

//create checked_iterators from containers, could be overloaded for arrays
template <class Container>
checked_iterator<typename Container::iterator> checked_begin(Container& c)
{
    return checked_iterator<typename Container::iterator>(c.begin(), c.end());
}

template <class Container>
checked_iterator<typename Container::const_iterator> checked_begin(const Container& c)
{
    return checked_iterator<typename Container::const_iterator>(c.begin(), c.end());
}

template <class Container>
checked_iterator<typename Container::iterator> checked_end(Container& c)
{
    return checked_iterator<typename Container::iterator>(c.end(), c.end());
}

template <class Container>
checked_iterator<typename Container::const_iterator> checked_end(const Container& c)
{
    return checked_iterator<typename Container::const_iterator>(c.end(), c.end());
}

Пример тестового кода:

#include <vector>
#include <list>
#include <iostream>
int main()
{
    typedef std::list<int> Container;
    try {
        Container v(10);
        checked_iterator<Container::iterator> it = checked_begin(v);
        std::advance(it, 6);
        std::cout << *it << '\n';
        std::advance(it, 4);
        std::cout << *it << '\n';
        const Container& r = v;
        checked_iterator<Container::const_iterator> cit = checked_begin(r);
        std::advance(cit, 11);
    }
    catch (const std::exception& e) {
        std::cout << e.what() << '\n';
    }
}

Что касается реализации функции safe_advance, это также может различать итераторы произвольного доступа и другие, такие как std::advance по соображениям эффективности:

#include <iterator>
#include <stdexcept>

namespace detail
{
    template <class Iter>
    void safe_advance_aux(
        Iter& it, Iter end, 
        typename std::iterator_traits<Iter>::difference_type n, 
        std::random_access_iterator_tag
        )
    {
        if (end - it < n) throw std::range_error("advance beyond end (ra)");
        it += n;
    }

    template <class Iter, class Tag>
    void safe_advance_aux(
        Iter& it, Iter end, 
        typename std::iterator_traits<Iter>::difference_type n, 
        Tag
        )
    {
        for (typename std::iterator_traits<Iter>::difference_type i = 0; i != n; ++i) {
            if (it == end) throw std::range_error("advance beyond end");
            ++it;
        }
    }
}

template <class Iter>
void safe_advance(Iter& it, Iter end, typename std::iterator_traits<Iter>::difference_type n)
{
    detail::safe_advance_aux(it, end, n, typename std::iterator_traits<Iter>::iterator_category());
}
2 голосов
/ 04 октября 2010

Я думаю, что вы пытаетесь решить не ту проблему здесь.

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

Вместо этого проверьте ваш код и выясните, ПОЧЕМУвызов advance может привести к тому, что он выйдет из конца вашего контейнера, и вместо этого исправить эту проблему с вашим кодом.

Немного больше контекста может дать нам больше шансов помочь.

...