119
перемещаются в коллекцию, а итератор применяется только для хранения служебной информации этих
функций. Базовый итератор и цикл итерации выглядят так:
class Iterator;
class Collection {
public:
Iterator*
Iterate();
//
Возвращает пассивный итератор
bool
More(Iterator*);
Foo*
Next(Iterator*);
};
Iterator* iter = collection->Iterate();
while (collection->More(iter))
f(collection->Next(iter));
Такие итераторы называются пассивными, поскольку сами по себе они не выполняют никаких
действий и предназначены лишь для хранения служебной информации.
Что лучше?
Выбор между активными и пассивными итераторами в основном зависит от стиля, но я предпочитаю
активные итераторы по нескольким причинам:
•
Законченный класс итератора проще использовать повторно, чем пару функций большого
класса.
•
Рано или поздно вам захочется предоставить несколько разных способов перебора содержимого
коллекции. Один и тот же общий интерфейс класса
Iterator
подойдет для любого способа, а в
форме функций класса для каждого типа перебора вам придется создавать пару новых функций.
•
Пассивные итераторы не имеют открытого интерфейса, однако клиентские объекты видят их
через адреса. Это выглядит довольно странно.
•
При равенстве всех остальных показателей я обычно предпочитаю активные итераторы,
поскольку они обеспечивают лучшую инкапсуляцию.
В коммерческих библиотеках классов можно встретить хорошие примеры обеих форм. В вопросе об
активных и пассивных итераторах отражается общий спор об активных и пассивных объектах, поэтому
в первую очередь следует учитывать собственные приемы проектирования.
Убогие, но распространенные варианты
Вряд ли вы встретите в коммерческих библиотеках классов итераторы именно в таком виде. У каждого
находится свой подход к этой теме. Ниже перечислены некоторые варианты, которые часто
встречаются в странствиях по С++, с краткими комментариями по поводу их достоинств и недостатков.
Мономорфные активные итераторы вне области действия
Даже жалко расходовать замечательный термин на такую простую концепцию. Итераторы называются
мономорфными, поскольку в них не используются виртуальные функции, и находятся вне области
действия, поскольку они не объявляются вложенными в коллекцию.
class Collection { ... };
class CollectionIterator {
private:
Collection*
coll;
public:
CollectionIterator(Collection*
coll);
bool
More();
Foo*
Next();
};
120
CollectionIterator iter(collection); //
Создать итератор
while (iter.More())
f(iter.Next());
Просто удивительно, что всего несколько строк программы порождает столько проблем:
•
При использовании класса, производного от
Collection
, каждый клиент должен знать, какие
новые классы итераторов должны использоваться вместо старого
CollectionIterator
.
•
Переменные класса итератора видны всем и каждому. Даже если они не составляют
государственной тайны, весь клиентский код придется перекомпилировать каждый раз, когда
вам захочется изменить реализацию итератора.
•
Занесение итераторов в стек противоречит некоторым стратегиям многопоточности,
рассматриваемым в следующей главе.
•
Многократное использование такого кода — задача мерзкая.
Учитывая все это, будет намного, намного лучше попросить класс коллекции: «Пожалуйста, сэр,
сделайте мне итератор» вместо того, чтобы самому создавать его в стеке. Невзирая на все проблемы,
этот тип итераторов часто встречается в коммерческих библиотеках классов.
Пассивные итераторы типа void*
Самая распространенная вариация на тему пассивных итераторов — не возиться с предварительным
объявлением класса итератора, а обмануть клиентов и внушить им, что на самом деле они имеют дело с
типом
void*
. Все это часто маскируется каким-нибудь красивым именем с помощью
typedef
, но
уродливый
void*
так легко не спрячешь.
typedef void* AprilInParis;
class Collection {
public:
AprilInParis
Iterate();
//
Возвращает загримированный void*
bool
More(AprilInParis&);
Foo*
Next(AprilInParis&);
};
Конечно, во внутреннем представлении хранится что-то более разумное, чем
void*
, поэтому код
реализации
Collection
должен постоянно преобразовывать
void*
к реальности. Не знаю как вас, но
лично меня приводит в ужас одна мысль о том, что клиентский код будет возиться с
void*
до его
преобразования. К тому же отладка такого кода дьявольски сложна, поскольку отладчик знает о том, с
чем он имеет дело, ничуть не больше клиента. Красивое название итератора не скроет изначального
уродства такого подхода.
Нетипизированные значения функции Next()
Многие классы итераторов пишутся в обобщенной форме для типа
void*
или какого-то абстрактного
базового класса. Клиент должен сам приводить значение, возвращаемое функцией
Next()
, обратно к
правильному типу — и горе ему, если он что-нибудь напутает. Шаблоны изобретались именно для этой
цели, так что теперь подобный бред уже нельзя оправдать.
Лучшие варианты
Начиная с этого места, я буду говорить об активных итераторах, однако все сказанное в равной мере
относится и к пассивным итераторам. Некоторые разновидности итераторов сильно зависят от
характеристик коллекции, но другие обладают большей универсальностью. Перечисляю их, не
придерживаясь какого-то определенного порядка.
Do'stlaringiz bilan baham: |