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 и т.д.
Здравствуйте, eugene0, Вы писали:
E>Хотелось, чтобы в процессе вычислений объект время от времени сообщал клиенту, сколько еще осталось считать, в процентах. Для этого он вызывает ф-ию по указателю m_pfun, которую клиент задал ему в самом начале работы. Но никак не удается присвоить m_pfun указатель на один из методов клиента, получается только на статический или на глобальную ф-ию, а это мне не подходит.
E>Всем, кто дочитал до этого места, спасибо за терпение. Посоветуйте, что здесь можно сделать? E>Хочется связать классы именно на уровне исходников, а не dll, com и т.д.
Здравствуйте, 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. Диалог вызывает вычислителя (который работает в отдельном потоке)
Спасибо за помощь.
Оба предложенных варианта не нравятся тем, что закладывают в класс вычислителя специфику gui-клиента. То есть там в обоих вариантах в конструкторе Calc — аргумент типа Dialog. А я хочу, чтобы считающий класс у меня был в виде двух файлов (h+cpp) и чтобы я сегодня мог прикрутить его к одному проекту, а завтра к другому, послезавтра — к третьему.
Единственное ограничение к клиенту — чтобы у него был метод, который можно будет вызвать, чтобы сообщить о ходе вычислений.
Или я слишком много хочу?
А в предложенном случае получается, что клиент должен быть жестко класса Dialog.
Здравствуйте, eugene0, Вы писали:
E>Оба предложенных варианта не нравятся тем, что закладывают в класс вычислителя специфику gui-клиента. То есть там в обоих вариантах в конструкторе Calc — аргумент типа Dialog. А я хочу, чтобы считающий класс у меня был в виде двух файлов (h+cpp) и чтобы я сегодня мог прикрутить его к одному проекту, а завтра к другому, послезавтра — к третьему. E>Единственное ограничение к клиенту — чтобы у него был метод, который можно будет вызвать, чтобы сообщить о ходе вычислений. E>А в предложенном случае получается, что клиент должен быть жестко класса Dialog.
Элементарно, Ватсон — ведь Страуструп дал нам наследование
сделай то, что вызывается, шаблонным параметром класса либо функции счета (последнее лучше — меньше будет дублирования сгенеренного кода, не зависящего от шаблона).
Тогда твой класс будет работать подобно алгоритмам STL, которые используют предикаты и прочие дела извне, которые могут быть представлены как обычными функциями, так и функторами со сложной логикой сборки (в том числе и методом класса вместе со ссылкой на объект этого класса).
Здравствуйте, jazzer, Вы писали:
J>сделай то, что вызывается, шаблонным параметром класса либо функции счета (последнее лучше — меньше будет дублирования сгенеренного кода, не зависящего от шаблона).
J>Тогда твой класс будет работать подобно алгоритмам STL, которые используют предикаты и прочие дела извне, которые могут быть представлены как обычными функциями, так и функторами со сложной логикой сборки (в том числе и методом класса вместе со ссылкой на объект этого класса).
Слова все знакомые, но если ты еще и каким-нибудь фрагментом кода их проиллюстрируешь, то сильно облегчишь мне жизнь
Здравствуйте, eugene0, Вы писали:
E>Здравствуйте, jazzer, Вы писали:
J>>сделай то, что вызывается, шаблонным параметром класса либо функции счета (последнее лучше — меньше будет дублирования сгенеренного кода, не зависящего от шаблона).
J>>Тогда твой класс будет работать подобно алгоритмам STL, которые используют предикаты и прочие дела извне, которые могут быть представлены как обычными функциями, так и функторами со сложной логикой сборки (в том числе и методом класса вместе со ссылкой на объект этого класса).
E>Слова все знакомые, но если ты еще и каким-нибудь фрагментом кода их проиллюстрируешь, то сильно облегчишь мне жизнь :)
E>Спасибо.
Здравствуйте, 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); }
// больше шаблонов здесь нет. реализация скрыта в срр-файле.
. . . . .
};
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, 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
}
Здравствуйте, 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(...);
...
}