Как вы обрабатываете несовпадения итераторов / const_iterator при передаче диапазонов в алгоритмы C ++? - PullRequest
2 голосов
/ 01 февраля 2012

Часть исходного кода для проекта, над которым я работаю, который отвечает за сжатие последовательности 'событий', выглядит следующим образом:

#include <iterator>
#include <list>

typedef int Event;

typedef std::list<Event> EventList;

struct Compressor {
    // Returns an iterator behind the last element which was 'eaten'
    virtual EventList::const_iterator eatEvents( const EventList &l ) = 0;
};

// Plenty of Compressor subclasses exist

void compressAndCopyEatenEvents( Compressor &c ) {
    EventList e;
    e.push_back( 1 );
    EventList::const_iterator newEnd = c.eatEvents( e );

    EventList eatenEvents;
    std::copy( e.begin(), newEnd, std::back_inserter( eatenEvents ) ); // barfs
}

Проблема здесь в том, что функция compressAndCopyEatenEvents имеет неконстантный список событий; этот список передается методу eatEvents, который принимает ссылку на const и возвращает const_iterator. Теперь функция compressAndCopyEatenEvenst хотела бы скопировать диапазон съеденных событий, поэтому она решает использовать некоторый алгоритм (std::copy здесь, который, разумеется, можно также заменить на правильный вызов конструктора std::list - точка является то, что эта проблема существует со всеми видами диапазонов).

К сожалению (?) Многие (если не все?) Диапазоны должны быть составлены из одного и того же типа итератора. Однако в приведенном выше коде e.begin () возвращает EventList::iterator (поскольку объект не является константным), а newEnd является EventList::const_iterator.

Есть ли здесь слабость дизайна, которая вызывает этот беспорядок? Как бы вы занялись этим?

Ответы [ 4 ]

4 голосов
/ 01 февраля 2012

В C ++ 03 единственный возможный способ - это разыграть.(что некрасиво, это недостаток дизайна, да).

std::copy( static_cast<EventList::const_iterator>e.begin(), newEnd, std::back_inserter( eatenEvents ) );

или есть другая именованная переменная:

EventList::const_iterator constBegin = e.begin();
std::copy(constBegin , newEnd, std::back_inserter( eatenEvents ) );

В C ++ 11 у вас есть cbegin и cend функций (которые всегда возвращают const_iterator с), так что вы бы просто сделали

std::copy( e.cbegin(), newEnd, std::back_inserter( eatenEvents ) );
2 голосов
/ 01 февраля 2012

Рассмотрите возможность использования

EventList::const_iterator b = e.begin();
std::copy( b, newEnd, std::back_inserter( eatenEvents ) );

Это вызовет корректную перегрузку list::begin(), и std::copy будет скомпилирован правильно.

1 голос
/ 01 февраля 2012

Посмотрите, что говорит мастер:

Скотт Мейерс в действующем STL

Пункт 26. Предпочитайте итератор const iterator, reverse_iterator и const_reverse_iterator.Хотя контейнеры поддерживают четыре типа итераторов, один из этих типов имеет привилегии, которые другие не имеют.Этот тип - итератор, итератор - особенный.the conversions that exist among iterator types.

typedef deque<int> IntDeque; //STL container and
typedef lntDeque::iterator Iter; // iterator types are easier
typedef lntDeque::const_iterator ConstIter; // to work with if you
// use some typedefs
Iter i;
ConstIter ci;
… //make i and ci point into
// the same container
if (i == ci ) ... //compare an iterator
// and a const_iterator

Пункт 27. Используйте расстояние и продвижение, чтобы преобразовать константные итераторы контейнера в итераторы.

typedef deque<int> IntDeque; //convenience typedefs
typedef lntDeque::iterator Iter;
typedef lntDeque::const_iterator ConstIter;
ConstIter ci; // ci is a const_iterator
…
Iter i(ci); // error! no implicit conversion from
// const_iterator to iterator
Iter i(const_cast<Iter>(ci)); // still an error! can't cast a
// const_iterator to an iterator

Что работаетэто заранее и расстояние

typedef deque<int> IntDeque; //as before
typedef IntDeque::iterator Iter;
typedef IntDeque::const_iterator ConstIter;
IntDeque d;
ConstIter ci;
… // make ci point into d
Iter i(d.begin()); // initialize i to d.begin()
Advance(i, distance(i, ci)) //move i up to where ci is
// (but see below for why this must
// be tweaked before it will compile)
0 голосов
/ 01 февраля 2012

Вы можете добавить вторую перегрузку eatEvents, чтобы компилятор автоматически выбрал правильный, чтобы сохранить постоянство:

virtual EventList::iterator eatEvents( EventList &l ) = 0;

(Один или оба из них могут быть не виртуальными и реализовываться в рамках одной базовой функции.)

Иногда это работает хорошо, хотя я не уверен, что здесь все идеально.

...