маленькие победы никому не нужного фронта

Nov 30, 2009 10:11

Закончена работа над классом DataSet: описание таблицы / результата запроса к базе данных. Вынесенные уроки (не все, но те, которые помню):

* Итераторы для классов-контейнеров реализуются при помощи шаблонных классов, в результате чего хедер вырастает до приличных размеров (в моём случае ок. 475 строк с комментами), ибо, как известно, шаблонные классы должны быть определены вместе с объявлением. Сами шаблоны нужны для реализации обычных и константных итераторов (как iterator и const_iterator в STL), потому как устройство у них в точности одинаковое, а вот типы данных - разные.
* Сами типы Iterator и ConstIterator объявляются для удобства (и завершённости конструкции) при помощи typedef в публичной секции класса-контейнера. Сам же шаблон итераторов может находиться в private, дабы избежать недоразумений и шаловливых ручек:

/// Regular iterator; can be used to modify containers.
typedef IteratorT_ Iterator;
/// Constant iterator; cannot modify referenced container.
typedef IteratorT_ ConstIterator;
* Преобразование типов из Iterator в ConstIterator делается с помощью шаблонного оператора (отрывок из "живого" кода):

/// Type casting (needed co cast an Iterator as ConstIterator).
template
operator IteratorT_()
{ return IteratorT_(*dataSet_, curRecord_); }

* Высокой связанности избежать вряд ли удастся, поэтому приходится идти на уступки в виде friend и mutable (см. следующий пункт насчёт mutable).
* Для того, чтобы итераторы не инвалидировались при удалении элементов, имеет смысл сделать отложенное удаление, со счётчиком ссылок. При этом, использовать родные const_iteratorы из STL в константных итераторах не представляется возможным: удаление может произойти в любой момент, в том числе, и когда константный итератор "уходит" от элемента, сбрасывая счётчик ссылок в 0 (производится чистка). Для всего этого требуется объявлять основной STL контейнер как mutable, ибо чистить мусор может быть нужно и в константном контейнере (в зависимости от контекста).
* Не забывать инкрементировать/декрементировать счётчик ссылок в конструкторе и деструкторе итераторов. Иначе - бумсь. Знаю, что идиотская ошибка, но именно она стоила мне несколько часов дебага. Вообще надо бы повнимательнее быть.
* Инвалидация итераторов необходима: в конце концов, надо что-то делать с удалёнными (а в нашем случае - помеченными к удалению) элементами. То же происходит с очисткой всего контейнера методами clear() (очистка данных) и reset() (очистка данных и полей). В первом случае записи, как уже сказано, помечаются на удаление, а итераторы смотрят на эти пометки и знают, действительны они или нет; во втором же, контейнер может иметь "версию", которая инкрементируется (в общем случае - меняется) каждый раз, когда нужно "убить" все итераторы. Они, разумеется, на эту версию тоже смотрят и хранят под сердцем её копию.
* Необходимо учитывать существование контейнера и оповещать итераторы о его удалении. Для этого было решено сделать общий реестр контейнеров в виде static std::set и функции проверки существования (действительности) указателя. Альтернативно, можно сделать реестр итераторов, но это сложнее и менее эффективно, так как итераторов обычно создаётся больше, чем контейнеров (чего стоят постоянные обращения к begin() и end()).
* Некоторые реализации STL (в моём случае - MinGW/G++) "зацикливают" std::list. То есть, end() + 1 == begin(), как-то так. Поскольку это не стандартизировано, но очень удобно, было решено повторить эту модель в библиотеке (уже задокументировав). Так что теперь итерация возможна в обоих направлениях, и при этом не нужно иметь дополнительный набор итераторов для обратной итерации (ср. reverse_iterator).

В остальном, конечно, всё это - всего лишь ещё одна рабочая неделя, в конце которой было что-то сделано. Припоминается недавний выпуск XKCD.

программизм, работа

Previous post Next post
Up