Здравствуйте Belegel, Вы писали:
B>Здравствуйте AIDS!
B>Спасибо за ответ.
B>Жарко, блин, туго соображаю...
B>>>Есть два класса: CListElem, банальный двунаправленный список, и CListManager, объявленный как friend для CListElem. В менеджере хранится указатель на первый элемент списка, объявленный protected, и отдавать его на публику (и через функции аля B>>>CListManager::GetFirst() или CListManager::GetNext() тоже) крайне нежелательно.
B>>>Задача реализовать механизм, который бы позволял вызывать для каждого элемента списка некоторую функцию, которая может быть определена как в классе CListElem, так и в его потомке.
AIDS>>1. Определите виртуальную функцию в классе CListElem, которая будет обрабатывать один элемент. AIDS>>2. В классе CListManager определите public функцию, которая будет вызывать функцию из класса ClistElem AIDS>>для каждого элемента списка. AIDS>>3. При использовании потомков CListElem — они должны перегрузить эту функци. для реализации собственной обработки.
B>А что делать, если в потомке CListElem надо будет обрабатывать элемент сразу несколькими способами (т.е. как бы несколько разных Process())? Делать у Process() параметр, и switch() по нему? А можно ли это сделать более изящно?
B>Спасибо еще раз. B>Belegel.
Ok, ну тогда все усложняется.
// объявляем абстрактный класс - действие над элементом списка
class CListElemAction
{
public:
virtual void Process( CListElem& elem )=0; // чисто виртуальная функция
};
Для каждого вида обработки элемента списка нужно определить
класс-потомок от CListElemAction и перегрузить функцию Process() — в ней логика обработки элемента.
В класс CListElem добавить новый член — CListElemAction* m_action; и метод для его инициализации.
Тогда будет что типа такого кода:
CListElemAction* action= new CSuperListAction(); // создаем действие
listElem->SetAction( action ); // listElem - указатель на элемент списка
В функции CListElem::Process() надо просто вызывать m_action->Process( this );
Ну и CListManager остается тем же.
Если есть желание — можно использовать STL, там все это уже есть: списки, итераторы, функциональные объекты (те что реализуют тот же принцип что и CListElemAction).
Есть два класса: CListElem, банальный двунаправленный список, и CListManager, объявленный как friend для CListElem. В менеджере хранится указатель на первый элемент списка, объявленный protected, и отдавать его на публику (и через функции аля
CListManager::GetFirst() или CListManager::GetNext() тоже) крайне нежелательно.
Задача реализовать механизм, который бы позволял вызывать для каждого элемента списка некоторую функцию, которая может быть определена как в классе CListElem, так и в его потомке.
Честно сказать, меня это несколько озадачило... Кабы мыжно было отдавать на публику элементы списка, все было бы просто, а тут... гм.
Здравствуйте Belegel, Вы писали:
B>Задача реализовать механизм, который бы позволял вызывать для каждого элемента списка некоторую функцию, которая может быть определена как в классе CListElem, так и в его потомке.
B>Честно сказать, меня это несколько озадачило... Кабы мыжно было отдавать на публику элементы списка, все было бы просто, а тут... гм.
А ты сделай итератор, и отдавай его, а не голый указатель, а доступ через итератор дай только к той части CListElem, которая данные содержит, а не сервисные данные списка. И все будет.
Здравствуйте Belegel, Вы писали:
B>Всем привет!
B>Вот такая проблемка у меня возникла:
B>Есть два класса: CListElem, банальный двунаправленный список, и CListManager, объявленный как friend для CListElem. В менеджере хранится указатель на первый элемент списка, объявленный protected, и отдавать его на публику (и через функции аля B>CListManager::GetFirst() или CListManager::GetNext() тоже) крайне нежелательно.
B>Задача реализовать механизм, который бы позволял вызывать для каждого элемента списка некоторую функцию, которая может быть определена как в классе CListElem, так и в его потомке.
B>Честно сказать, меня это несколько озадачило... Кабы мыжно было отдавать на публику элементы списка, все было бы просто, а тут... гм.
B>Помогите, плз, разобраться...
B>Всем заранее спасибо, B>Belegel.
1. Определите виртуальную функцию в классе CListElem, которая будет обрабатывать один элемент.
2. В классе CListManager определите public функцию, которая будет вызывать функцию из класса ClistElem
для каждого элемента списка.
3. При использовании потомков CListElem — они должны перегрузить эту функци. для реализации собственной обработки.
class CListElem
{
...
public:
virtual void Process();
...
};
class CListManager
{
...
public:
void ProcessList();
...
};
void CListManager::ProcessList()
{
for( перебираем список )
{
curListElem->Process();
}
}
Жарко, блин, туго соображаю... :no:
B>>Есть два класса: CListElem, банальный двунаправленный список, и CListManager, объявленный как friend для CListElem. В менеджере хранится указатель на первый элемент списка, объявленный protected, и отдавать его на публику (и через функции аля B>>CListManager::GetFirst() или CListManager::GetNext() тоже) крайне нежелательно.
B>>Задача реализовать механизм, который бы позволял вызывать для каждого элемента списка некоторую функцию, которая может быть определена как в классе CListElem, так и в его потомке.
AIDS>1. Определите виртуальную функцию в классе CListElem, которая будет обрабатывать один элемент. AIDS>2. В классе CListManager определите public функцию, которая будет вызывать функцию из класса ClistElem AIDS>для каждого элемента списка. AIDS>3. При использовании потомков CListElem — они должны перегрузить эту функци. для реализации собственной обработки.
А что делать, если в потомке CListElem надо будет обрабатывать элемент сразу несколькими способами (т.е. как бы несколько разных Process())? Делать у Process() параметр, и switch() по нему? А можно ли это сделать более изящно?
Спасибо за ответ.
B>>Задача реализовать механизм, который бы позволял вызывать для каждого элемента списка некоторую функцию, которая может быть определена как в классе CListElem, так и в его потомке.
B>>Честно сказать, меня это несколько озадачило... Кабы можно было отдавать на публику элементы списка, все было бы просто, а тут... гм.
Kaa>А ты сделай итератор, и отдавай его, а не голый указатель, а доступ через итератор дай только к той части CListElem, которая данные содержит, а не сервисные данные списка. И все будет.
Хорошая мысль, спасибо -- я об этом не думал. Только я не уверен, что такое решение будет оптимальным в моем конкретном случае. На самом деле, мне вообще не нужен доступ к данным каждого элемента списка вне менеджера. Мне просто надо вызвать для каждого элемента списка функцию-член класса этого элемента, которую укажет (тем или иным образом) человек, который будет это все использовать.
Брр... Прости, если я туплю, эта жара меня добивает %(...
Спасибо еще раз.
Belegel.
Re[4]: Двунаправленный список
От:
Аноним
Дата:
06.07.02 09:02
Оценка:
Здравствуйте, AIDS!
Спасибо за ответ.
B>>>>Есть два класса: CListElem, банальный двунаправленный список, и CListManager, объявленный как friend для CListElem. В менеджере хранится указатель на первый элемент списка, объявленный protected, и отдавать его на публику (и через функции аля B>>>>CListManager::GetFirst() или CListManager::GetNext() тоже) крайне нежелательно.
B>>>>Задача реализовать механизм, который бы позволял вызывать для каждого элемента списка некоторую функцию, которая может быть определена как в классе CListElem, так и в его потомке.
AIDS>>>1. Определите виртуальную функцию в классе CListElem, которая будет обрабатывать один элемент. AIDS>>>2. В классе CListManager определите public функцию, которая будет вызывать функцию из класса ClistElem AIDS>>>для каждого элемента списка. AIDS>>>3. При использовании потомков CListElem — они должны перегрузить эту функци. для реализации собственной обработки.
(...skip...)
B>>А что делать, если в потомке CListElem надо будет обрабатывать элемент сразу несколькими способами (т.е. как бы несколько разных Process())? Делать у Process() параметр, и switch() по нему? А можно ли это сделать более изящно?
AIDS>Ok, ну тогда все усложняется.
AIDS>
AIDS>// объявляем абстрактный класс - действие над элементом списка
AIDS>class CListElemAction
AIDS>{
AIDS>public:
AIDS> virtual void Process( CListElem& elem )=0; // чисто виртуальная функция
AIDS>};
AIDS>
AIDS>Для каждого вида обработки элемента списка нужно определить AIDS>класс-потомок от CListElemAction и перегрузить функцию Process() — в ней логика обработки элемента. AIDS>В класс CListElem добавить новый член — CListElemAction* m_action; и метод для его инициализации. AIDS>Тогда будет что типа такого кода: AIDS>
AIDS>CListElemAction* action= new CSuperListAction(); // создаем действие
listElem->>SetAction( action ); // listElem - указатель на элемент списка
AIDS>
AIDS>В функции CListElem::Process() надо просто вызывать m_action->Process( this ); AIDS>Ну и CListManager остается тем же.
Спасибо большое, это как раз то, что мне нужно.
AIDS>Если есть желание — можно использовать STL, там все это уже есть: списки, итераторы, функциональные объекты (те что реализуют тот же принцип что и CListElemAction).
Желание то есть... STL -- хорошая штука. Однако, смысл был в том, чтобы написать самому. (Ну, или хотя бы разобраться.)
Здравствуйте Belegel, Вы писали:
B>Мне просто надо вызвать для каждого элемента списка функцию-член класса этого элемента, которую укажет (тем или иным образом) человек, который будет это все использовать.
B>Брр... Прости, если я туплю, эта жара меня добивает %(...
Ты выражаешься на редкость четко и конкретно. Нет, все понятно. Все делается примерно таком образом. Т.к. ты не привел описания типов, я введу свои, и на их примре мы все увидим.
class CListManager;
class CListElem
{
private:
friend class CListManager;
CListElem() : i( 0 ) {}
void DoSomething() { /*modify data members, for example*/ i++; }
private:
// data membersint i;
};
class CListManager
{
public:
CListManager() : pFirst( new CListElem() ) {};
~CListManager() { delete pFirst; }
void IncAll()
{
for_each( &CListElem::DoSomething );
}
private:
template <class FN>
void for_each( FN fn )
{
for (
CListElem* p = pFirst /*begin()*/;
p != NULL/*end()*/;
p = GetNext()/*p++*/ )
{
(p->*fn)();
}
}
CListElem* GetNext() { return NULL; }
private:
CListElem* pFirst;
};
int main(int argc, char* argv[])
{
CListManager mg;
mg.IncAll();
return 0;
}
Спасибо за ответ!
B>>Мне просто надо вызвать для каждого элемента списка функцию-член класса этого элемента, которую укажет (тем или иным образом) человек, который будет это все использовать.
B>>Брр... Прости, если я туплю, эта жара меня добивает %(... Kaa>Ты выражаешься на редкость четко и конкретно. Нет, все понятно. Все делается примерно таком образом. Т.к. ты не привел описания типов, я введу свои, и на их примре мы все увидим.
(...)
Гениально! А я-то полез уже копать в сторону функциональных объектов (если это так по русски звучит)... Ну хоть с ними разобрался немного -- и то хорошо...
Однако, не все так радужно, как казалось на первый взгляд... :(
Только что до меня дошло, что я не учел один момент -- необходимо иметь возможность обрабатывать значения, которые будут возвращать вызываемые функции. (Например, для того, чтобы посчитать сумму значений всех элементов списка.)
Я думаю о примерно таком решении:
(код не проверял)
class CListManager;
class CListElem
{
private:
friend class CListManager;
CListElem() : i( 0 ) {}
int GetValue() { return i; }
void IncValue() { i++; }
private:
// data membersint i;
};
class CListManager
{
public:
CListManager() : pFirst( new CListElem() ), pCur(pFirst) {};
~CListManager() { delete pFirst; }
// Или если RET -- тип возвр. значения, то MSVC на него ругаться (как если бы он
// вообще не был упомянут в заголовке функции) не будет?template <class FN, class RET>
RET CallCurrentElementFunction(FN fn, RET* /*unused*/)
{
return (pCur->*fn)();
}
void RewindToStart()
{
pCur = pStart;
}
BOOL AtEndNow()
{
return pCur->GetNext() == NULL;
}
void GoToNextElement()
{
pCur = pCur->GetNext();
}
private:
CListElem* pFirst;
CListElem* pCur;
};
int main(int argc, char* argv[])
{
CListManager manager;
int sum = 0;
manager.RewindToStart();
while(AtEndNow() == FALSE)
{
sum += manager.CallCurrentElementFunction(&CListElem::GetValue, (int*)0);
manager.GoToNextElement();
}
return 0;
}
То есть, я держу внутри CListManager указатель на текущий элемент списка, даю пользователь возможность его двигать вперед-назад и вызывать для текущего элемента любую его функцию.
Хотя теперь получается, что я все-таки даю доступ извне к данным элементов списка... Хорошо, допустим я напишу функцию int CListManager::GetListSum(), а функцию CListManager::CallElementFunction() сделаю закрытой. Но теперь я теряю возможность вызывать извне любую функцию для элемента списка. Замкнутый круг... Можно, конечно, объявить список приватным и заставить пользователя наследовать от CListManager, если он хочет что-то делать со списком... но лучший ли это выход?
Здравствуйте Belegel, Вы писали:
B>Хотя теперь получается, что я все-таки даю доступ извне к данным элементов списка... Хорошо, допустим я напишу функцию int CListManager::GetListSum(), а функцию CListManager::CallElementFunction() сделаю закрытой. Но теперь я теряю возможность вызывать извне любую функцию для элемента списка. Замкнутый круг... Можно, конечно, объявить список приватным и заставить пользователя наследовать от CListManager, если он хочет что-то делать со списком... но лучший ли это выход?
Ну, по-перше. Не любую. Такой подход разрешит пользователю вызвать только функции, имеющие определенный прототип. Т.е., если функций-членов с определенным прототипом много — то это может быть проблемой, если же их там — парочка, тогда все проще, и твоя защита прокатит (в нашем случае мы можем вызывать только функции-члены с прототипом
void FnName();
Во-вторых. Я так понимаю, и я это отразил в своем коде — CListElem — чисто сервисный тип, и наружу по хорошему торчать вообще не должен. Если это так (а в моем коде это так), то методы (т.к. они private) может вызывать только экземпляр класса CListManager. А через него ты и будешь иметь такую возможность (для текущего эл-та вызывать все, что хочешь. Но это сильно испортит интерфейс CListManager, поэтому такие вещи делаются через что-то типа умных указателей, например, через итератор.
Но! Итератор можно всегда сделать частным для класса-manager-а, и т.о. пользователи твоего класса не смогут вызывать что попало относительно элемента, а ты сам (в рамках CListManager) — с легкостью.
Спасибо за ответ.
B>>Хотя теперь получается, что я все-таки даю доступ извне к данным элементов списка... Хорошо, допустим я напишу функцию int CListManager::GetListSum(), а функцию CListManager::CallElementFunction() сделаю закрытой. Но теперь я теряю возможность вызывать извне любую функцию для элемента списка. Замкнутый круг... Можно, конечно, объявить список приватным и заставить пользователя наследовать от CListManager, если он хочет что-то делать со списком... но лучший ли это выход?
Kaa>Ну, по-перше. Не любую. Такой подход разрешит пользователю вызвать только функции, имеющие определенный прототип. Т.е., если функций-членов с определенным прототипом много — то это может быть проблемой, если же их там — парочка, тогда все проще, и твоя защита прокатит (в нашем случае мы можем вызывать только функции-члены с прототипом Kaa>
void FnName();
Ну, не только void, а с любым возвращаемым типом, который задается через шаблон:
template <class FN, class RET>
RET CallCurrentElementFunction(FN fn, RET* /*unused*/)
//^^^
{
return (pCur->*fn)();
}
Или я что-то путаю? К тому же, насколько я понимаю, твой код тоже позволяет вызывать только функции-члены с прототипом void FnName(). (На самом деле не только void, но возвращаемое значение никак не используется.) Я не прав?
В конце концов, можно определить шаблоны для функций с одним и двумя параметрами (как наиболее часто исполюзующиеся). А если пользователю приспичит делать функцию с большим числом параметров, то пусть передает их в структуре. Хотя, конечно, это не так красиво как хотелось бы.
По второму пункту:
Kaa>Во-вторых. Я так понимаю, и я это отразил в своем коде — CListElem — чисто сервисный тип, и наружу по хорошему торчать вообще не должен.
Это действительно так.
Kaa>Если это так (а в моем коде это так), то методы (т.к. они private) может вызывать только экземпляр класса CListManager. А через него ты и будешь иметь такую возможность (для текущего эл-та вызывать все, что хочешь. Но это сильно испортит интерфейс CListManager, поэтому такие вещи делаются через что-то типа умных указателей, например, через итератор.
Kaa>Но! Итератор можно всегда сделать частным для класса-manager-а, и т.о. пользователи твоего класса не смогут вызывать что попало относительно элемента, а ты сам (в рамках CListManager) — с легкостью.
Честно говоря, я не очень въехал.
В последнем абзаце моего предыдущего письма (который процитирован наверху этого) имелась ввиду примерно такая конструкция:
class CListManager
{
private:
CListElem *pStart;
CListElem *pCur;
protected:
template <class FN, class RET>
RET CallCurrentElementFunction(FN fn, RET* unused = NULL)
// ^^^^^^ Подставит ли MSVC правильный тип
// (int) сам, если написать CallCurrentElementFunction(&CListElem::GetValue);, или он
// возьмет тип от NULL (который, как я понимаю, тоже int (или long))?
{
return (pCur->*fn)();
}
void RewindToStart()
{
pCur = pStart;
}
BOOL AtEndNow()
{
return pCur->GetNext() == NULL;
}
void StepToNextElement()
{
if(pCur == NULL) return;
pCur = pCur->GetNext();
}
public:
CListManager() : pStart( new CListElem() ), pCur(pStart) { };
~CListManager() { delete pStart; }
};
Пользователь, который хочет ввести новую функцию для работы со списком должен унаследовать свой класс от CListManager присмерно таким образом:
class CMyManager : public CListManager
{
public:
CMyManager() : CListManager() { };
int CountListSum()
{
int sum = 0;
RewindToStart();
while(AtEndNow() == FALSE)
{
sum += CallCurrentElementFunction(&CListElem::GetValue);
GoToNextElement();
}
return sum;
}
}
Одним из недостатков такой реализации (на мой взгляд) является то, что пользователь будет способен дать доступ извне к отдельным элементам списка (посредством функции CallCurrentElementFunction()), в то время, как по идеологическим соображениям доступ должен быть только к списку в целом.
Другим недостатком является то, что приходится заставлять пользователя наследовать и от CListElement, и от CListManager когда он хочет добавить новую функциональность списку. Но, по всей видимости, этого избежать не удастся идеологическим соображениям, упомянутым в предыдужем абзаце. По крайней мере я не представляю как это можно сделать.
Не мог бы ты рассказать немного поподробнее о том, что имел ввиду ты?
(...)
B> template <class FN, class RET> B> RET CallCurrentElementFunction(FN fn, RET* unused = NULL) B> // ^^^^^^ Подставит ли MSVC правильный тип B>// (int) сам, если написать CallCurrentElementFunction(&CListElem::GetValue);, или он B>// возьмет тип от NULL (который, как я понимаю, тоже int (или long))?
.
Чудес не бывает, и если использовать параметр по умолчанию, то надо явно указывать тип. Соответственно здесь будет вот так:
B> sum += CallCurrentElementFunction(&CListElem::GetValue);
sum += CallCurrentElementFunction<int (*CListElem::GetValue)(), int)
(&CListElem::GetValue);