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


Автор: Ravenan 24.5.2006, 12:04
Всем доброе время суток.

Последние пару лет писал на C#, а тут пришлось на С++ вернуться. Не могу реализовать довольно простую как мне кажется вешь, просто не верится что это не выполнимо  smile  Перерыл ваш форум но исчерпывающего ответа не нашел. Суть проблемы - как реализовать на C++ аналог делегата на C#. То есть, у меня допустим есть класс А и класс В. Нужно в метод класса В передать метод класса А чтоб он его вызвал при определенных условиях. Что-то вроде callcack функции, но в ее роли выступает метод. Методы не статические. Плиз подскажите решение или киньте ссылкой. 

ЗЫ Имеется ввиду не передавая в класс B указатель на класс А  

Автор: Mayk 24.5.2006, 14:47
Цитата(Ravenan @  24.5.2006,  12:04 Найти цитируемый пост)
Имеется ввиду не передавая в класс B указатель на класс А 

Придется. Но можно замаскировать.

Ну например вот так:
Код

struct Callback{
    virtual void operator()();
};

template<class C>
struct MemberCallback : public Callback
{   
     typedef void (C::*MemFun)();
     MemFun func;
     C* ptr;

     MemberCallback(C* instance, MemFun member)  :ptr(instance), func(member) { }
    
    virtual void operator()(){
        (ptr->*func)();
    }
};

template<class C>
MemberCallback<C> makeMemberCallback(C* instance, typename MemberCallback<C>::MemFun member) {
 return MemberCallback<C>(instance, member);
}

//
// собсно проверка
//

struct B{
  void foobar(Callback bc){ bc(); } //класс B знать не знает о наличии класса А
};

struct A{
  void d(){
     B b; b.foobar(makeMemberCallback(this, &A::d)); 
  }
};

int main(){
    A a; a.d();
}

(comeau скомпилировал)


То есть мы делаем класс каллбэк, а он сам решает что и кому ему передать управление - ф-ции-члену класса, вызвать какую либо обычную ф-цию или ещё что.

ps. кстати, этот вопрос всплывает не в первый раз. на моей памяти во второй. это не укор автору темы, это небольшая статистика  smile 


    

Автор: Ravenan 24.5.2006, 15:42
Mayk
Большое спасибо  smile  

Автор: Earnest 24.5.2006, 15:48
В С++ делегатов нет. Пока. Но можно воспользоваться boost::function - это очень гибкий механизм, который наверняка войдет в следующую реинкарнацию STL. 
Это примерно то же самое, что написал Mayk,  но гораздо мощнее - любые параметры, любые возвр. значения и т.д. 

Автор: MAKCim 24.5.2006, 16:00
см. Александреску - обобщенный функтор
тоже любое возвращаемое значение, любые параметры (практически их произвольное количество)
он может инкапсулировать обычный функтор, указатель на функцию, указатель на функцию-член
имхо, обобщеннее трудно себе представить 

Автор: Ravenan 24.5.2006, 16:09
Mayk
Радость омрачилась ошибкой линкера  smile 

Error    1    error LNK2001: unresolved external symbol "public: virtual void __thiscall Callback::operator()(void)" (??RCallback@@UAEXXZ)    qq.obj    

компилятор - VS2005 

Автор: MAKCim 24.5.2006, 18:47
Код

struct Callback{
    virtual void operator()() {}
};
template<class C>
struct MemberCallback : public Callback
{   
     typedef void (C::*MemFun)();
     MemFun func;
     C* ptr;
     MemberCallback(C* instance, MemFun member)  :ptr(instance), func(member) { }
    
    virtual void operator()(){
        (ptr->*func)();
    }
};
template<class C>
MemberCallback<C> makeMemberCallback(C* instance, typename MemberCallback<C>::MemFun member) {
 return MemberCallback<C>(instance, member);
}
//
// собсно проверка
//
struct B{
  void foobar(Callback& bc){ bc(); } //класс B знать не знает о наличии класса А
};
struct A{
  void d(){
     B b;
    MemberCallback<A> obj=makeMemberCallback(this,&A::d); 
    b.foobar(obj); 
  }
};
int main(){
    A a; a.d();
}
 

Автор: Ravenan 24.5.2006, 19:08
MAKCim
Спасибо, теперь все ок!

Вот только думаю раз на такие ухищрения приходится идти - может сама архитектура неправильная?    smile 
У меня есть класс который прнимает сообщения с клавиатуры через DirectInput. Я хотел бы дать ему хеш-таблицу с callback методами для разных клавиш. Самой обработкой естественно другой класс заниматься будет.
Не подскажите - нормальная вообще практика подобная? 
То есть это стандартный подход при проектировании или подобных ситуаций в С++ стоит избегать? 

Автор: MAKCim 24.5.2006, 21:21
Цитата

Не подскажите - нормальная вообще практика подобная? 

вполне нормальная
сделать что-то типа
Код

std::map<char,functor_t> map_;

и вызывать потом
Код

void some_class::call(char key)
{
    map_[key]();
}
 

Автор: Ravenan 25.5.2006, 17:15
MAKCim
Прошу помощи в решении еще одной возникнувшей проблемы  smile 

Все прекрасно работает если метод вызывается непосредственно там куда его передали вроде
Код

void foobar(Callback& bc)

    bc(); 
}

А если bc сохранить в каком-то поле класса и вызвать потом из другого метода, то приложение вываливается.
Код

struct B
{
    Callback *p;
    
    void foobar(Callback& bc)
    { 
        p = &bc;
    }

    void foobar1()
    { 
        (*p)();
    }
};

В дебагере видно что хотя сам указатель p не изменился при переходе в другую функцию, значение структуры Callback на которую он указывает самопроизвольно изменилось.

Объясните пожалуйста причину подобного побочного еффекта  smile    

Автор: likehood 25.5.2006, 17:24
Цитата(Ravenan @  25.5.2006,  18:15 Найти цитируемый пост)
    void foobar(Callback& bc)    
    {  
        p = &bc;    
    }    
    void foobar(Callback& bc)    
    {  
        (*p)();    
    }


Может у функций все же разные имена? 

Автор: Ravenan 25.5.2006, 17:32
baronp
да, сорри, подправил 

Автор: likehood 25.5.2006, 17:36
а как ты вызываешь 1-ю функцию? 

Автор: Ravenan 25.5.2006, 17:48
Код

class A
{
private:
    B bInst;

    void handler()
    {
    }


public:
    A()
    {
        bInst->foobar(MemberCallback<A>(this, &A::handler));
    }

    void Call()
    {
        bInst->foobar1();
    }
};
  

Автор: likehood 25.5.2006, 17:57
При вызове foobar в кострукторе A() ты создаешь временный объект, который разрушается после выхода из foobar. Выход: в классе B вместо указателья обявить переменную, но тогда нужен default-конструктор в MemberCallback и там же нужен copy-коструктор.

Добавлено @ 18:01 
На счет замены указателья это я погорячился. Надо в первый foobar передавать не ссылку, а указатель, при этом объект MemberCallback  в конструкторе А придется создавать динамически. Тогда придется думать об удалении этого объекта (возможно auto_ptr). 

Автор: Ravenan 25.5.2006, 18:12
baronp
Спасибо, работает!  smile  

Автор: MAKCim 25.5.2006, 18:14
странно что у тебя
такое прокатывает
Цитата

Код

bInst->foobar(MemberCallback<A>(this, &A::handler));


тут создается временный объект, но
foobar принимает ссылку на Callback, а по стандарту возможна
только константная ссылка на временный объект 

Автор: Ravenan 25.5.2006, 18:34
MAKCim
еще раз доказывает что каждый компилятор такое моменты решает как ему угодно а не  по стандарту. Все прекрасно работало пока не уничтожался этот временный объект 

Автор: Void 25.5.2006, 18:34
MAKCim, одно из нестандартных расширений MSVC (автор, по всей видимости, пользуется именно им).
Но таки лучше параметр сделать константной ссылкой и оператор () тоже константным. 

Автор: MAKCim 25.5.2006, 21:07
Цитата

одно из нестандартных расширений MSVC (автор, по всей видимости, пользуется именно им)

имхо, лучше ими не пользоваться
ввиду непереносимости в общем случае 

Автор: takedo 26.5.2006, 06:14
Ravenan
Цитата

Вот только думаю раз на такие ухищрения приходится идти - может сама архитектура неправильная? 

если пишешь приложение оконное - стопудов неправильная!!! Я также как и ты сперва поизвращался с передачей адреса функции класса - ввсё нормально заработало, но потом ещё раз проанализировав(а информацию я брал в какой-то статье на RSDN, называлась она что-то типа самые быстрые делегаты на C++) я понял, что это неприемлемо для оокнонных приложений, потому как каждый компилятор делает такие вещи по своему(см. статью которую ты ещё не нашел smile ), и переделал это на манер Windows, почитав внимательно товарища Рихтера! А на манер Windows - это просто посылать сообщения! И просто и работает стопудово! smile

Добавлено @ 06:20 
Странно, помню, что читал совершенно другую статью, а нашёл сейчас вот эту smile 
http://www.rsdn.ru/article/cpp/delegates.xml 

Автор: Ravenan 26.5.2006, 11:49
takedo
http://www.rsdn.ru/article/cpp/delegates.xml  - довольно познавательная статья. Прочитай я ее раньше - не напрягал бы всех smile

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

Автор: takedo 29.5.2006, 10:07
Ravenan, дело твоё smile  

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