Указатель на метод класса
От: eugene0 Россия  
Дата: 07.01.04 15:42
Оценка:
2moderator: в вопросе упоминается MFC, но мне кажется, что по сути своей вопрос больше по принципам ООП, а MFC здесь только для примера.

Есть класс (решает задачу вычисления по определенному алгоритму):
typedef void (*PREPORTBACK)(int);

class CCalc
{
...
public:
  PREPORTBACK m_pfun;

  int Calc(int arg); // главный метод: вычисления по некоторому алгоритму
};

int CCalc::Calc
{
  ...
  // где-то внутри цикла
  if(m_pfun) (*m_pfun)(...);
  ...
}

Dialog-based клиент для этого класса:
class CCalcDlg : public CDialog
{
...
public:
  CCalc m_CalcObj;
  void ReportBack(int nPercents); 
};

...

BOOL CCalcDlg::OnInitDialog()
{
  ...
  m_CalcObj.m_pfun = ReportBack; // здесь компилятор говорит, что нельзя так преобразовать типы указателей  :( 
  ...
}

Ну и где-то еще в CCalcDlg вызываем m_CalcObj.Calc(..), чтобы собственно начать считать. Хотелось, чтобы в процессе вычислений объект время от времени сообщал клиенту, сколько еще осталось считать, в процентах. Для этого он вызывает ф-ию по указателю m_pfun, которую клиент задал ему в самом начале работы. Но никак не удается присвоить m_pfun указатель на один из методов клиента, получается только на статический или на глобальную ф-ию, а это мне не подходит.

Всем, кто дочитал до этого места, спасибо за терпение. Посоветуйте, что здесь можно сделать?
Хочется связать классы именно на уровне исходников, а не dll, com и т.д.

Исправлено форматирование. -- ПК
Re: Указатель на метод класса
От: Bell Россия  
Дата: 07.01.04 16:15
Оценка: -1
Здравствуйте, eugene0, Вы писали:

Вариант "в лоб":

typedef void (CCalcDlg*PREPORTBACK)(int);
Любите книгу — источник знаний (с) М.Горький
Re: Указатель на метод класса
От: yeti Россия  
Дата: 07.01.04 16:25
Оценка:
Здравствуйте, eugene0, Вы писали:

E>Хотелось, чтобы в процессе вычислений объект время от времени сообщал клиенту, сколько еще осталось считать, в процентах. Для этого он вызывает ф-ию по указателю m_pfun, которую клиент задал ему в самом начале работы. Но никак не удается присвоить m_pfun указатель на один из методов клиента, получается только на статический или на глобальную ф-ию, а это мне не подходит.


E>Всем, кто дочитал до этого места, спасибо за терпение. Посоветуйте, что здесь можно сделать?

E>Хочется связать классы именно на уровне исходников, а не dll, com и т.д.

class Dialog
{
public:
    void change_progress(int i)
    {
        cout << i << endl;
    }
};

class Calc
{
public:
    Calc(Dialog *d) : master(d), show_progress(Dialog::change_progress) {}
    void calc(int n)
    {
        for(int i=0;i<n;++i)
        {
            (master->*show_progress)(i);
        }
    }
    Dialog *master;
    void(Dialog::*show_progress)(int);
};

int main()
{
    Dialog d;
    Calc c(&d);
    c.calc(13);
    return 0;
}

Оно?
... << RSDN@Home 1.1.2 beta 2 >>
Re[2]: Указатель на метод класса
От: yeti Россия  
Дата: 07.01.04 16:33
Оценка:
Кстати, а чем такой вариант (без указателей на функцию-член) не подходит?

class Dialog
{
public:
    void change_progress(int i)
    {
        cout << i << endl;
    }
};

class Calc
{
public:
    Calc(Dialog *d) : master(d) {}
    void calc(int n)
    {
        for(int i=0;i<n;++i)
        {
            master->change_progress(i);
        }
    }
    Dialog *master;
};

int main()
{
    Dialog d;
    Calc c(&d);
    c.calc(13);
    return 0;
}
... << RSDN@Home 1.1.2 beta 2 >>
Re: Указатель на метод класса
От: Кодт Россия  
Дата: 07.01.04 16:59
Оценка: 1 (1)
Здравствуйте, eugene0, Вы писали:

E>Есть класс (решает задачу вычисления по определенному алгоритму):

E>Dialog-based клиент для этого класса:
E>Ну и где-то еще в CCalcDlg вызываем m_CalcObj.Calc(..), чтобы собственно начать считать. Хотелось, чтобы в процессе вычислений объект время от времени сообщал клиенту, сколько еще осталось считать, в процентах. Для этого он вызывает ф-ию по указателю m_pfun, которую клиент задал ему в самом начале работы. Но никак не удается присвоить m_pfun указатель на один из методов клиента, получается только на статический или на глобальную ф-ию, а это мне не подходит.

E>Всем, кто дочитал до этого места, спасибо за терпение. Посоветуйте, что здесь можно сделать?

E>Хочется связать классы именно на уровне исходников, а не dll, com и т.д.

Помимо чисто языковых нюансов (где какой тип объявить), предлагаю тебе подумать о временной диаграмме.
А оттуда уже станет ясна реализация.

1. Диалог вызывает вычислителя в модальном режиме:
CCalcDlg::OnRun()
{
  m_CalcObj.Calc();
  // в этой точке уже все посчитано
}

Это означает, что CalcObj должен (сам или с помощью диалога) качать помпу оконных сообщений, иначе программа "замерзнет".

2. Диалог вызывает вычислителя (который работает в отдельном потоке)
CCalcDlg::OnRun()
{
  AfxGetMainWnd()->EnableWindow(false); // эффект модальности
  m_CalcObj.Calc();
}
CCalcDlg::onCalculated()
{
  AfxGetMainWnd()->EnableWindow(true);
}




Один из способов взаимодействия — просто послать диалогу сообщение.
const UINT WM_CalcInProgress = ::RegisterWindowMessage("CalcInProgress");
const UINT WM_CalcFinished = ::RegisterWindowMessage("CalcFinished");

CCalc::onProgress(int percent)
{
  m_pClientWindow->SendMessage(WM_CalcInProgress, WPARAM(percent), LPARAM(this));
}
CCalc::onFinish()
{
  m_pClientWindow->SendMessage(WM_CalcInProgress, 0, LPARAM(this));
}

void CCalcDlg::OnCalcInProgress(WPARAM percent, LPARAM lp)
{
  ASSERT(lp == LPARAM(&m_Calc));
  m_wndProgressBar.SetValue(percent);
}
void CCalcDlg::OnCalcFinished(WPARAM, LPARAM lp)
{
  ASSERT(lp == LPARAM(&m_Calc));
  m_wndProgressBar.SetValue(100);
  .....
}

Другой способ — выполнить callback-метод.
////////////////////////////////////////////////
// все клиенты Calc должны иметь такой интерфейс
struct ICalcEvents
{
  virtual void onCalcProgress(int percent) = 0;
  virtual void onCalcFinish() = 0;
};

class CCalc
{
  ICalcEvents* m_sink;
public:
  void setEventSink(ICalcEvents* sink) { m_sink = sink; }

  void onProgress(int percent) { if(m_sink) m_sink->onCalcProgress(percent); }
  void onFinish() { if(m_sink) m_sink->onCalcFinish(); }
  .....
};

CCalcDlg::OnInitDialog()
{
  .....
  m_CalcObj.setEventSink(this);
  .....
}

///////////////////////////
// либо мы делаем делегатов
class CCalc
{
  boost::function1<void,int> m_onProgress;
  boost::function0<void> m_onFinish;
public:
  void setOnProgress(boost::function1<void,int> const& fn) { m_onProgress = fn; }
  void setOnFinish(boost::function0<void> const& fn) { m_onFinish = fn; }

  void onProgress(int percent) { m_onProgress(percent); }
  void onFinish() { m_onFinish(); }
};

CCalcDlg::OnInitDialog()
{
  .....
  m_CalcObj.setOnProgress(boost::bind(&CCalcDlg::onProgress, this));
  m_CalcObj.setOnFinish(boost::bind(&CCalcDlg::onFinish, this));
  .....
}

Можно возложить задачу прокачки помпы на сам диалог, то есть выполнять ее в CCalcDlg::onProgress.
Перекуём баги на фичи!
Re[3]: Указатель на метод класса
От: eugene0 Россия  
Дата: 07.01.04 17:42
Оценка:
Здравствуйте, yeti, Вы писали:

Спасибо за помощь.
Оба предложенных варианта не нравятся тем, что закладывают в класс вычислителя специфику gui-клиента. То есть там в обоих вариантах в конструкторе Calc — аргумент типа Dialog. А я хочу, чтобы считающий класс у меня был в виде двух файлов (h+cpp) и чтобы я сегодня мог прикрутить его к одному проекту, а завтра к другому, послезавтра — к третьему.
Единственное ограничение к клиенту — чтобы у него был метод, который можно будет вызвать, чтобы сообщить о ходе вычислений.

Или я слишком много хочу?

А в предложенном случае получается, что клиент должен быть жестко класса Dialog.
Re[4]: Указатель на метод класса
От: yeti Россия  
Дата: 07.01.04 18:12
Оценка: 5 (2)
Здравствуйте, eugene0, Вы писали:

E>Оба предложенных варианта не нравятся тем, что закладывают в класс вычислителя специфику gui-клиента. То есть там в обоих вариантах в конструкторе Calc — аргумент типа Dialog. А я хочу, чтобы считающий класс у меня был в виде двух файлов (h+cpp) и чтобы я сегодня мог прикрутить его к одному проекту, а завтра к другому, послезавтра — к третьему.

E>Единственное ограничение к клиенту — чтобы у него был метод, который можно будет вызвать, чтобы сообщить о ходе вычислений.
E>А в предложенном случае получается, что клиент должен быть жестко класса Dialog.

Элементарно, Ватсон — ведь Страуструп дал нам наследование

class ProgressOwner // :-)
{
public:
    virtual void change_progress(int i) = 0;
};

class Calc
{
public:
    Calc(ProgressOwner *d) : master(d) {}
    void calc(int n)
    {
        for(int i=0;i<n;++i)
        {
            master->change_progress(i);
        }
    }
    ProgressOwner *master;
};

class Dialog : public ProgressOwner
{
public:
    virtual void change_progress(int i)
    {
        cout << i << endl;
    }
};

int main()
{
    Dialog d;
    Calc c(&d);
    c.calc(13);
    return 0;
}

Теперь Calc ничего не знает о диалоге
... << RSDN@Home 1.1.2 beta 2 >>
Re: Указатель на метод класса
От: jazzer Россия Skype: enerjazzer
Дата: 07.01.04 20:51
Оценка:
сделай то, что вызывается, шаблонным параметром класса либо функции счета (последнее лучше — меньше будет дублирования сгенеренного кода, не зависящего от шаблона).

Тогда твой класс будет работать подобно алгоритмам STL, которые используют предикаты и прочие дела извне, которые могут быть представлены как обычными функциями, так и функторами со сложной логикой сборки (в том числе и методом класса вместе со ссылкой на объект этого класса).
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[2]: Указатель на метод класса
От: eugene0 Россия  
Дата: 08.01.04 04:40
Оценка:
Здравствуйте, jazzer, Вы писали:

J>сделай то, что вызывается, шаблонным параметром класса либо функции счета (последнее лучше — меньше будет дублирования сгенеренного кода, не зависящего от шаблона).


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


Слова все знакомые, но если ты еще и каким-нибудь фрагментом кода их проиллюстрируешь, то сильно облегчишь мне жизнь

Спасибо.
Re[3]: Указатель на метод класса
От: jazzer Россия Skype: enerjazzer
Дата: 08.01.04 09:51
Оценка: 2 (1)
Здравствуйте, eugene0, Вы писали:

E>Здравствуйте, jazzer, Вы писали:


J>>сделай то, что вызывается, шаблонным параметром класса либо функции счета (последнее лучше — меньше будет дублирования сгенеренного кода, не зависящего от шаблона).


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


E>Слова все знакомые, но если ты еще и каким-нибудь фрагментом кода их проиллюстрируешь, то сильно облегчишь мне жизнь :)


E>Спасибо.


да легко


class Calc
{
...
   template <class CB>
   void do_calc(int arg, CB progress_cb)
   {
     ... //всякая полезная работа

     progress_cb(30); //30%
   
     ... // etc
   }
...
};

void static_f(int);
class Dialog
{
   void show_progress(int);
};

main ()
{
   Calc c;
   do_calc(123, static_f);
   Dialog d;
   do_calc(456, std::bind1st(std::mem_fun1_ref(&Dialog::show_progress), d)); // STL
   do_calc(789, (_1 ->* &Dialog::show_progress)(d));                         // Boost::lambda
   do_calc(314, bind(&Dialog::show_progress, ref(d), _1));                   // Boost::bind
}


ДУМАЮ, последнее бустом делается еще красивее
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[2]: Указатель на метод класса
От: Кодт Россия  
Дата: 08.01.04 10:07
Оценка: 2 (1)
Здравствуйте, jazzer, Вы писали:

J>сделай то, что вызывается, шаблонным параметром класса либо функции счета (последнее лучше — меньше будет дублирования сгенеренного кода, не зависящего от шаблона).


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


Это жестко. Придется выносить весь код калькулятора в хедер.
template<class Client>
class Calc
{
private:
  Client* m_client;
  void progress(int percent) { m_client->onProgress(percent); } // у класса-клиента должен быть такой метод
  void finish() { m_client->onFinish(); } // и такой

  . . . . .

public:
  void setClient(Client* client) { m_client = client; }
  void run();
};

///////////

class CCalcDlg : public CDialog
{
  . . . . .
  Calc<CCalcDlg> m_calc;
  . . . . .
public: // или friend class Calc<CCalcDlg>
  void onProgress(int p);
  void onFinish();
};


Лучше сделать полиморфного посредника между нешаблонными Calc и CCalcDlg.
Например, на базе boost::function (см. мой предыдущий пост).
Или такого:
class CalcEvents // прототип класса - обработчика событий от калькулятора
{
public:
  virtual ~CalcEvents() {}
  virtual void progress(int percent) = 0;
  virtual void finish() = 0;
};

template<class Client>
class CalcEventsImp : public CalcEvents // обработчик, транслирующий события в вызовы методов клиента
{
private:
  Client* m_client;
public:
  void progress(int percent) { m_client->onProgress(percent); } // у класса-клиента должен быть такой метод
  void finish() { m_client->onFinish(); } // и такой

  CalcEventsImp(Client* client) { m_client = client; }
};

template<class Client>
std::auto_ptr<CalcEvents> makeCalcEvents(Client* client) // фабричная функция
{
  return std::auto_ptr<CalcEvents>(new CalcEventsImp<Client>(client));
}

/////////////////////

class Calc
{
private:
  std::auto_ptr<CalcEvents> events; // указатель на приемник событий
  // обращения к приемнику инкапсулированы - чтобы в коде не париться
  void progress(int p) { if(events != NULL) events->progress(p); }
  void finish() { if(events != NULL) events->finish(); }

public:
  // универсальный метод для настройки событий (мало ли, кто и как реализует CalcEvents)
  setEvents(std::auto_ptr<CalcEvents> e) { events = std::auto_ptr<CalcEvents>(e); }

  // метод, заточенный под клиента (это шаблон, который элементарно инлайнится)
  template<class T> void setClient(T* client) { setEvents(makeCalcEvents(client); }

  // больше шаблонов здесь нет. реализация скрыта в срр-файле.
  . . . . .
};
Перекуём баги на фичи!
Re[3]: Указатель на метод класса
От: jazzer Россия Skype: enerjazzer
Дата: 08.01.04 10:36
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Здравствуйте, jazzer, Вы писали:


J>>сделай то, что вызывается, шаблонным параметром класса либо функции счета (последнее лучше — меньше будет дублирования сгенеренного кода, не зависящего от шаблона).


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


К>Это жестко. Придется выносить весь код калькулятора в хедер.


Лучше разбить код калькулятора на части, между которыми вызывается прогресс.
Либо действительно сделать проксю, как ты предлагаешь, но использовать ее иначе:

class Calc
{
private:
   class PercProxyBase // прототип класса - обработчика событий от калькулятора
   {
   public:
     virtual void operator()(int percent) = 0;
   }

   template <class CB>
   class PercProxy: public PercProxyBase  // шаблон для хранения функтора (а лучше - ссылки на него)
   {
     CB callback;
   public:
     PercProxy(CB cb) : callback(cb) {};
     void operator()(int percent) { callback(percent); }
   };
...
public:
   template <class CB>
   void do_calc(int arg, CB progress_cb)
   {
     PercProxy cb(progress_cb);
     do_actual_calc(arg, cb);
   }
private:
   void do_actual_calc(int arg, PercProxyBase& progress_cb) // реализация в срр
   {
     ... //всякая полезная работа

     progress_cb(30); //30%
   
     ... // etc
   }
...
};

void static_f(int);
class Dialog
{
   void show_progress(int);
};

main ()
{
   Calc c;
   do_calc(123, static_f);
   Dialog d;
   do_calc(456, std::bind1st(std::mem_fun1_ref(&Dialog::show_progress), d)); // STL
   do_calc(789, (_1 ->* &Dialog::show_progress)(d));                         // Boost::lambda
   do_calc(314, bind(&Dialog::show_progress, ref(d), _1));                   // Boost::bind
}


ВСЕ, что можно, инлайнится + обошлись без new
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re: Указатель на метод класса
От: Zuka Россия  
Дата: 14.01.04 05:17
Оценка:
Здравствуйте, eugene0, Вы писали:

E>2moderator: в вопросе упоминается MFC, но мне кажется, что по сути своей вопрос больше по принципам ООП, а MFC здесь только для примера.


E>Есть класс (решает задачу вычисления по определенному алгоритму):

E>
E>typedef void (*PREPORTBACK)(int);

E>class CCalc
E>{
E>...
E>public:
E>  PREPORTBACK m_pfun;

E>  int Calc(int arg); // главный метод: вычисления по некоторому алгоритму
E>};

E>int CCalc::Calc
E>{
E>  ...
E>  // где-то внутри цикла
E>  if(m_pfun) (*m_pfun)(...);
E>  ...
E>}
E>

E>Dialog-based клиент для этого класса:
E>
E>class CCalcDlg : public CDialog
E>{
E>...
E>public:
E>  CCalc m_CalcObj;
E>  void ReportBack(int nPercents); 
E>};

E>...

E>BOOL CCalcDlg::OnInitDialog()
E>{
E>  ...
E>  m_CalcObj.m_pfun = ReportBack; // здесь компилятор говорит, что нельзя так преобразовать типы указателей  :( 
E>  ...
E>}
E>

E>Ну и где-то еще в CCalcDlg вызываем m_CalcObj.Calc(..), чтобы собственно начать считать. Хотелось, чтобы в процессе вычислений объект время от времени сообщал клиенту, сколько еще осталось считать, в процентах. Для этого он вызывает ф-ию по указателю m_pfun, которую клиент задал ему в самом начале работы. Но никак не удается присвоить m_pfun указатель на один из методов клиента, получается только на статический или на глобальную ф-ию, а это мне не подходит.

E>Всем, кто дочитал до этого места, спасибо за терпение. Посоветуйте, что здесь можно сделать?

E>Хочется связать классы именно на уровне исходников, а не dll, com и т.д.

Может лучше передать указатель на класс.
class  IReportable
{
    virtual void ReportBack(int nPercents) = 0;    
}

class CCalcDlg : public CDialog, IReportable
{
...
public:
  CCalc m_CalcObj;
  virtual void ReportBack(int nPercents); 
};

...

BOOL CCalcDlg::OnInitDialog()
{
  ...
  m_CalcObj.m_pReporter = this;
//  m_CalcObj.m_pfun = ReportBack; // здесь компилятор говорит, что нельзя так преобразовать типы указателей  :( 
  ...
}

class CCalc
{
...
public:
  IReportable * m_pReporter;

  int Calc(int arg); // главный метод: вычисления по некоторому алгоритму
};

int CCalc::Calc
{
  ...
  // где-то внутри цикла
  if(m_pReporter) 
        m_pReporter->ReportBack(...);
  ...
}
... << RSDN@Home 1.0 beta 4 >>
Re: Указатель на метод класса
От: Юнусов Булат Россия  
Дата: 14.01.04 06:59
Оценка:
Здравствуйте, eugene0, Вы писали:

Мои пять копеек

struct Calculator
{
    boost::function1<void, const int> Update;

    void Calculate()
    {
        for(int percent = 0; percent < 100; percent += 10)
        {
            if (Update)
            {
                Update(percent);
            }
        }
    }

};

struct Dialog
{
    Calculator calc_;

    void OnCalculate() 
    {
        calc_.Update = boost::bind(&Dialog::OnUpdate, this, _1);
        calc_.Calculate();
        calc_.Update.clear();
    }

    void OnUpdate(const int percent)
    {
        std::cout << percent << std::endl;
    }
};

int _tmain(int argc, _TCHAR* argv[])
{
    Dialog dlg;
    dlg.OnCalculate();

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