Re[3]: Двунаправленный список
От: AIDS Великобритания  
Дата: 05.07.02 21:47
Оценка: 3 (1)
Здравствуйте 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 — они должны перегрузить эту функци. для реализации собственной обработки.


AIDS>>
AIDS>>class CListElem
AIDS>>{
AIDS>>...
AIDS>>public:
AIDS>>virtual void Process();
AIDS>>...
AIDS>>};

AIDS>>class CListManager
AIDS>>{
AIDS>>...
AIDS>>public:
AIDS>>void ProcessList();
AIDS>>...

AIDS>>};

AIDS>>void CListManager::ProcessList()
AIDS>>{
AIDS>>    for( перебираем список )
AIDS>>    { 
AIDS>>          curListElem->Process();

AIDS>>    }
AIDS>>}
AIDS>>


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).

HTH
AIDS
Двунаправленный список
От: Belegel  
Дата: 05.07.02 13:39
Оценка:
Всем привет!

Вот такая проблемка у меня возникла:

Есть два класса: CListElem, банальный двунаправленный список, и CListManager, объявленный как friend для CListElem. В менеджере хранится указатель на первый элемент списка, объявленный protected, и отдавать его на публику (и через функции аля
CListManager::GetFirst() или CListManager::GetNext() тоже) крайне нежелательно.

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

Честно сказать, меня это несколько озадачило... Кабы мыжно было отдавать на публику элементы списка, все было бы просто, а тут... гм.

Помогите, плз, разобраться...

Всем заранее спасибо,
Belegel.
Re: Двунаправленный список
От: Kaa Украина http://blog.meta.ua/users/kaa/
Дата: 05.07.02 13:47
Оценка:
Здравствуйте Belegel, Вы писали:

B>Задача реализовать механизм, который бы позволял вызывать для каждого элемента списка некоторую функцию, которая может быть определена как в классе CListElem, так и в его потомке.


B>Честно сказать, меня это несколько озадачило... Кабы мыжно было отдавать на публику элементы списка, все было бы просто, а тут... гм.


А ты сделай итератор, и отдавай его, а не голый указатель, а доступ через итератор дай только к той части CListElem, которая данные содержит, а не сервисные данные списка. И все будет.
Алексей Кирдин
Re: Двунаправленный список
От: AIDS Великобритания  
Дата: 05.07.02 14:00
Оценка:
Здравствуйте 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();

    }
}


HTH
AIDS
Re[2]: Двунаправленный список
От: Belegel  
Дата: 05.07.02 15:09
Оценка:
Здравствуйте AIDS!

Спасибо за ответ.

Жарко, блин, туго соображаю... :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 — они должны перегрузить эту функци. для реализации собственной обработки.


AIDS>
AIDS>class CListElem
AIDS>{
AIDS>...
AIDS>public:
AIDS>virtual void Process();
AIDS>...
AIDS>};

AIDS>class CListManager
AIDS>{
AIDS>...
AIDS>public:
AIDS>void ProcessList();
AIDS>...

AIDS>};

AIDS>void CListManager::ProcessList()
AIDS>{
AIDS>    for( перебираем список )
AIDS>    { 
AIDS>          curListElem->Process();

AIDS>    }
AIDS>}
AIDS>


А что делать, если в потомке CListElem надо будет обрабатывать элемент сразу несколькими способами (т.е. как бы несколько разных Process())? Делать у Process() параметр, и switch() по нему? А можно ли это сделать более изящно?

Спасибо еще раз.
Belegel.
Re[2]: Двунаправленный список
От: Belegel  
Дата: 05.07.02 15:32
Оценка:
Здравствуйте, Kaa!

Спасибо за ответ.

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.
Re[3]: Двунаправленный список
От: Kaa Украина http://blog.meta.ua/users/kaa/
Дата: 06.07.02 10:37
Оценка:
Здравствуйте Belegel, Вы писали:

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


B>Брр... Прости, если я туплю, эта жара меня добивает %(...

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

class CListManager;

class CListElem
{
private:
  friend class CListManager;

  CListElem() : i( 0 ) {}

  void DoSomething() { /*modify data members, for example*/ i++; }
private:
// data members
  int 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;
}
Алексей Кирдин
Re[4]: Двунаправленный список
От: Belegel  
Дата: 06.07.02 13:01
Оценка:
Здравствуйте, Kaa!

Спасибо за ответ!

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


B>>Брр... Прости, если я туплю, эта жара меня добивает %(...

Kaa>Ты выражаешься на редкость четко и конкретно. Нет, все понятно. Все делается примерно таком образом. Т.к. ты не привел описания типов, я введу свои, и на их примре мы все увидим.

(...)

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

Большое спасибо!
Belegel.
Re[4]: Двунаправленный список
От: Belegel  
Дата: 06.07.02 14:25
Оценка:
Здравствуйте, Kaa!

Однако, не все так радужно, как казалось на первый взгляд... :(

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

Я думаю о примерно таком решении:
(код не проверял)

class CListManager;

class CListElem
{
private:
  friend class CListManager;

  CListElem() : i( 0 ) {}

  int GetValue() { return i; }
  void IncValue() { i++; }

private:
// data members
  int 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.
Re[5]: Двунаправленный список
От: Kaa Украина http://blog.meta.ua/users/kaa/
Дата: 06.07.02 15:33
Оценка:
Здравствуйте Belegel, Вы писали:

B>Хотя теперь получается, что я все-таки даю доступ извне к данным элементов списка... Хорошо, допустим я напишу функцию int CListManager::GetListSum(), а функцию CListManager::CallElementFunction() сделаю закрытой. Но теперь я теряю возможность вызывать извне любую функцию для элемента списка. Замкнутый круг... Можно, конечно, объявить список приватным и заставить пользователя наследовать от CListManager, если он хочет что-то делать со списком... но лучший ли это выход?


Ну, по-перше. Не любую. Такой подход разрешит пользователю вызвать только функции, имеющие определенный прототип. Т.е., если функций-членов с определенным прототипом много — то это может быть проблемой, если же их там — парочка, тогда все проще, и твоя защита прокатит (в нашем случае мы можем вызывать только функции-члены с прототипом
void FnName();


Во-вторых. Я так понимаю, и я это отразил в своем коде — CListElem — чисто сервисный тип, и наружу по хорошему торчать вообще не должен. Если это так (а в моем коде это так), то методы (т.к. они private) может вызывать только экземпляр класса CListManager. А через него ты и будешь иметь такую возможность (для текущего эл-та вызывать все, что хочешь. Но это сильно испортит интерфейс CListManager, поэтому такие вещи делаются через что-то типа умных указателей, например, через итератор.

Но! Итератор можно всегда сделать частным для класса-manager-а, и т.о. пользователи твоего класса не смогут вызывать что попало относительно элемента, а ты сам (в рамках CListManager) — с легкостью.
Алексей Кирдин
Re[6]: Двунаправленный список
От: Belegel  
Дата: 06.07.02 17:56
Оценка:
Здравствуйте, Kaa!

Спасибо за ответ.

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 когда он хочет добавить новую функциональность списку. Но, по всей видимости, этого избежать не удастся идеологическим соображениям, упомянутым в предыдужем абзаце. По крайней мере я не представляю как это можно сделать.

Не мог бы ты рассказать немного поподробнее о том, что имел ввиду ты?

Заранее спасибо,
Belegel.
Re[7]: Двунаправленный список
От: Belegel  
Дата: 06.07.02 18:07
Оценка:
Я писал:

(...)

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))?

Стормозил, извини...
Неверно понял твое письмо
Автор: Belegel
Дата: 06.07.02
.
Чудес не бывает, и если использовать параметр по умолчанию, то надо явно указывать тип. Соответственно здесь будет вот так:

B> sum += CallCurrentElementFunction(&CListElem::GetValue);

sum += CallCurrentElementFunction<int (*CListElem::GetValue)(), int)
(&CListElem::GetValue);

если я ничего не напутал с указателем на функцию.

Belegel.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.