Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Общие вопросы > Проектирование итератора


Автор: Lazin 17.12.2007, 14:23
Возникла следующая задача, есть сложная структура данных, элементы которой можно читать только последовательно, для этого случая идеально подходит паттерн "итератор". Вопрос в том, где его лучше описывать - внутри класса для доступа к которому он используется, или лучше его описать отдельно. Я понимаю, что оба решения примерно одинаковы, вопрос в том как будет более аккуратно, с учетом возможных последующих изменений.
Код

///итератор для доступа к данным хранящимся в объекте класса Block
///класс Block должен иметь семантику CBlockPacker-а
template <class Block>
class CBlockIterator
{
const Block & m_owner;

CBlockIterator(const Block &o) : m_owner(o)//конструктор получает ссылку на объект класса содержащий данные
{
}

//методы итератора используют методы CBlockPacker 
//для доступа к данным через параметр шаблона Block
};

//класс - содержащий данные
template <class Format, class Packer>
class CBlockPacker
{
//методы для доступа к данным:

    class Iterator//то-же самое что и в случае с CBlockIterator только без шаблонов
    {
         //....
    };
};

template<class Format, class Packer>
class CBlockFTPacker : public CBlockPacker<Format, Packer>
{
//....
};

Автор: UnrealMan 17.12.2007, 15:01
Ну, лично мне бы не понравилось видеть определение одного большого класса (где много членов) внутри определения другого большого класса. А вот поместить внутрь определения класса только объявление вложенного класса - это можно. В случае ошибок в диагностическом сообщении компилятора будет хорошо видно, к чему относится вложенный класс.

Автор: Lazin 17.12.2007, 15:14
Цитата(UnrealMan @  17.12.2007,  15:01 Найти цитируемый пост)
А вот поместить внутрь определения класса только объявление вложенного класса - это можно. В случае ошибок в диагностическом сообщении компилятора будет хорошо видно, к чему относится вложенный класс.

Я думал примерно так, 
Код

.....
class CBlockPacker
{
//....
typdef CBlockIterator<CBlockPacker> Iterator;
};

здесь может быть проблема если итератор использует защищенные методы и объявлен другом CBlockPacker-а, если я унаследую от CBlockPacker какой-то класс, то отношение дружбы потеряется, и итератор для производного класса работать перестанет.

Автор: UnrealMan 17.12.2007, 16:06
Цитата(Lazin @  17.12.2007,  15:14 Найти цитируемый пост)
Я думал примерно так, 

Наверное, шаблонными аргументами тут должно быть что-то другое.
Некоторые компиляторы в сообщении об ошибке могут сослаться на CBlockIterator<....>, а не CBlockPacker<....>::Iterator. Вот я о чём.


Цитата(Lazin @  17.12.2007,  15:14 Найти цитируемый пост)
здесь может быть проблема если итератор использует защищенные методы и объявлен другом CBlockPacker-а, если я унаследую от CBlockPacker какой-то класс, то отношение дружбы потеряется, и итератор для производного класса работать перестанет. 

Что-то я не понял, где он перестанет работать. Приведи пример.

Автор: baldina 17.12.2007, 18:22
описывать снаружи имеет смысл только в случае, если у тебя несколько классов с одинаковым интерфейсом доступа, для которых ты можешь использовать похожую реализацию итераторов.
насчет дружбы идея понятна, понятна и проблема. потому лучше их и не дружить.

Автор: Sartorius 17.12.2007, 18:29
 ИМХО лучше помещать итератор в класс, с которым он будет работь. К чему плодить шаблоны?  smile  Нда и в STL такая реализация (чем не пример для подражания)

Автор: UnrealMan 17.12.2007, 18:35
Цитата(baldina @  17.12.2007,  18:22 Найти цитируемый пост)
насчет дружбы идея понятна, понятна и проблема.

А вот мне проблема не понятна.

Цитата(baldina @  17.12.2007,  18:22 Найти цитируемый пост)
потому лучше их и не дружить. 

А как тогда обращаться к непубличным членам?

Добавлено через 6 минут и 12 секунд
Цитата(Sartorius @  17.12.2007,  18:29 Найти цитируемый пост)
Нда и в STL такая реализация (чем не пример для подражания) 

У STL разные реализации.

Добавлено через 11 минут и 54 секунды
Например, в реализации STL от GNU, которая имеется у меня, итератор списка является невложенным классом.

Автор: JackYF 17.12.2007, 19:46
Цитата(UnrealMan @  17.12.2007,  18:35 Найти цитируемый пост)
Например, в реализации STL от GNU, которая имеется у меня, итератор списка является невложенным классом. 

Подтверждаю: и у меня тоже.

Так что... не факт, не факт. Я бы сказал так: если хватает публичных методов/полей класса для построения итератора - делаем внешний, если не хватает, то тут... не знаю, я склоняюсь ко внешнему, но, имхо, и так, и так сойдёт.

Автор: Lazin 18.12.2007, 09:31
Цитата(UnrealMan @  17.12.2007,  15:01 Найти цитируемый пост)
Ну, лично мне бы не понравилось видеть определение одного большого класса (где много членов) внутри определения другого большого класса. 

Цитата(Sartorius @  17.12.2007,  18:29 Найти цитируемый пост)
ИМХО лучше помещать итератор в класс, с которым он будет работь. К чему плодить шаблоны?  smile  Нда и в STL такая реализация (чем не пример для подражания) 

вопросов больше нет
Цитата(UnrealMan @  17.12.2007,  16:06 Найти цитируемый пост)
Что-то я не понял, где он перестанет работать. Приведи пример.

это я малость ступил, я подумал что отношение дружбы не наследуется, поэтому тот-же шаблон не будет работать для производного класса(так как другом он объявлен в базовом), но никто не мешает мне объявить итератор другом и в производном.
ps
есть еще один вариант реализации итератора, это когда ф-ии для перебора элементов коллекции, находятся в классе коллекции.

Автор: Earnest 18.12.2007, 10:24
Цитата(JackYF @  17.12.2007,  20:46 Найти цитируемый пост)
Я бы сказал так: если хватает публичных методов/полей класса для построения итератора - делаем внешний

Не вижу связи. Вложенный класс точно так же не может обращаться к непубличным методам, если он не друг. И с шаблонами все то же, только параметры немного в другом месте описаны.

Так что речь только об именах. Вложенный класс - меньше имен. Но. Если в дальнейшем возникнет необходимость в неполных объявлениях, на то вложенный клас без включения хедера не сошлешься, тогда как на внешний - элементарно.
Резюме примерно такое: с точки зрения функуциональности - по барабану. Здесь нужно учитывать скорее публичность данного класса. Если это дело нужно сугубо локально, то вложенный класс - хорошее решение. Если речь идет о более широком использовании, лучше внешний клас. А чтобы крепче связать его с целевым контейнером и не громоздить многоэтажные имена, можно все засунуть в пространство имен.

Важнее сделать нормальный интерфейс: раз уж называешь iterator, и то вести себя он должен как кошерный STL-итератор. Здесь может помочь boost::iterator_adapter - очень хорошая вешь, три четверти работы за тебя сделает.

Автор: baldina 18.12.2007, 16:10
Цитата(UnrealMan @ 17.12.2007,  18:35)
Цитата(baldina @  17.12.2007,  18:22 Найти цитируемый пост)
насчет дружбы идея понятна, понятна и проблема.

А вот мне проблема не понятна.

Цитата(baldina @  17.12.2007,  18:22 Найти цитируемый пост)
потому лучше их и не дружить. 

А как тогда обращаться к непубличным членам?

дружба - очень сильная связь. надо иметь достаточно оснований для связи посредством дружбы. именно по этой причине дружба в С++ сделана нетранзитивной. в данном случае дружба дает только доступ к внутреннему устройству класса, что не менее эффективно достигается вложенным классом. а если итератор более общий, то класс, для которого он предназначен, будет иметь некий интерфейс для использования итераторами (или чем-то инициализировать итератор). здесь дружба и не понадобится. а в силу того что дружба не транзитивна, красиво и правильно использовать класс итератора с другими классами не выйдет.

Автор: UnrealMan 18.12.2007, 22:39
Цитата(baldina @  18.12.2007,  16:10 Найти цитируемый пост)
в данном случае дружба дает только доступ к внутреннему устройству класса, что не менее эффективно достигается вложенным классом.

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

Цитата(baldina @  18.12.2007,  16:10 Найти цитируемый пост)
дружба - очень сильная связь. надо иметь достаточно оснований для связи посредством дружбы. именно по этой причине дружба в С++ сделана нетранзитивной.

Что ещё за транзитивность дружбы такая?

Автор: baldina 18.12.2007, 22:59
Цитата

Что ещё за транзитивность дружбы такая?


не транзитивность.
это означает, что если A дружит с B, а B дружит с C, то это не означает, что А дружит с С

Автор: baldina 18.12.2007, 23:39
Цитата(UnrealMan @ 18.12.2007,  22:39)
Цитата(baldina @  18.12.2007,  16:10 Найти цитируемый пост)
в данном случае дружба дает только доступ к внутреннему устройству класса, что не менее эффективно достигается вложенным классом.

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

Согласен, я был неправ.

Автор: chipset 19.12.2007, 08:44
Цитата(Lazin @  17.12.2007,  04:23 Найти цитируемый пост)
элементы которой можно читать только последовательно, для этого случая идеально подходит паттерн "итератор".

ИМХО, тут больше подходит "stack." Что ты будешь делать я вызову *(iter+=2)?

Цитата(Earnest @  18.12.2007,  00:24 Найти цитируемый пост)
Резюме примерно такое: с точки зрения функуциональности - по барабану. 


Цитата(Earnest @  18.12.2007,  00:24 Найти цитируемый пост)
Важнее сделать нормальный интерфейс: раз уж называешь iterator, и то вести себя он должен как кошерный STL-итератор. Здесь может помочь boost::iterator_adapter - очень хорошая вешь, три четверти работы за тебя сделает.

Адназначна  smile 

Автор: Lazin 19.12.2007, 09:18
Цитата(chipset @  19.12.2007,  08:44 Найти цитируемый пост)
ИМХО, тут больше подходит "stack." Что ты будешь делать я вызову *(iter+=2)?

а я не реализую операцию += smile

Цитата(baldina @  18.12.2007,  16:10 Найти цитируемый пост)
дружба - очень сильная связь. надо иметь достаточно оснований для связи посредством дружбы. 

Представь что итератор пользуется несколькими ф-ями из контейнера, для перехода по элементам. Эти ф-ии логично сделать закрытыми, так как клиентам класса не нужно их использовать напрямую, а для этого приходится делать класс другом. Дружба действительно оч. сильная связь, следующая после наследования по силе, но итератор по определению должен быть связан с классом коллекции, так как это всего-лишь вспомогательный класс для доступа к данным коллекции.

Автор: chipset 19.12.2007, 11:08
Цитата(Lazin @  18.12.2007,  23:18 Найти цитируемый пост)
Дружба действительно оч. сильная связь, следующая после наследования по силе,

Дружба сильнее наследования поскольку она снимает private а наследование (и то, только public) снимает лишь protected.


А вообще чем больше смотрю на твою проблему, тем более она мне напоминает паттерн proxy. А ещё точнее secure proxy. Потому-что все-таки итератор ассоциируется с random access. Если ты запретишь += то я, как юзер, ваще не вникну в чем дело. 

Да! Мне кажется что основной класс похож на storage и ничего не делает кроме того что хранит данные. ИМХО имеет смысл тупо написать:

Код

class DataProxyStack
{
public:
SomeDataType Pop();
//остальная фигня
];

А уже потом декорировать как хочешь итераторами и всякой фигней. 

ЗЫ. Пока модератора нет, вот ещё кусок кода на обозрение публики.

Код

namespace detail
{
struct PureStorage
{
//все методы публичны
};

}

class PureStorageProxy
{
detail::PureStorage _data;
public:
//хитровыкрученные выверты с безопасностью данных и т.д.
};


Автор: Lazin 19.12.2007, 13:01
Цитата(chipset @  19.12.2007,  11:08 Найти цитируемый пост)
Да! Мне кажется что основной класс похож на storage и ничего не делает кроме того что хранит данные.

Даже не хранит, а является посредником, но это не важно. Важно то что элементы storage можно получать только строго по порядку, так как туда записываются только изменения(как в cvs репозиторииsmile). Т.е. что-бы вычислить следующий элемент нужно знать предыдущий. 
Методы для вычисления очередного элементы содержит класс - storage? так как только он знает формат их хранения, а итератор их вызывает, и хранит промежуточные данные.
Так что сдесь скорее такая схема
Код

class Storage
{
..методы для перемещения по хранилищу
};

class Iterator
{
..промежуточное состояние, нужно для перехода к следующему элементу
const Storage& st;
void Next();
bool End();
Element Get();
Iterator(const Storage& s);
};

и использовать можно будет так
Код

for (Iterator i(mystorage); !i.End(); i.Next() )
{ i.Get()->....
}

жаль в языке нельзя ограничить отношение дружбы несколькими нужными ф-ями

Автор: UnrealMan 19.12.2007, 22:08
Цитата(chipset @  19.12.2007,  11:08 Найти цитируемый пост)
Потому-что все-таки итератор ассоциируется с random access. 

Когда это понятия iterator и random access iterator стали означать одно и то же?

Автор: Earnest 25.12.2007, 19:53
Цитата(Lazin @  19.12.2007,  14:01 Найти цитируемый пост)
жаль в языке нельзя ограничить отношение дружбы несколькими нужными ф-ями 

Дык выдели этот функционал в отдельный класс и именно с ним дружи, если уж за чистоту борешься...

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)