Как реализовать обратный вызов в C ++? - PullRequest
13 голосов
/ 01 августа 2010

Я хочу реализовать класс в c ++, который имеет обратный вызов.

Так что я думаю, что мне нужен метод, который имеет 2 аргумента:

  • целевой объект.(скажем, * myObj)
  • указатель на функцию-член целевого объекта.(так что я могу сделать * myObj-> memberFunc ();)

Условия:

  • myObj может быть из любого класса.

  • функция-член, которая будет функцией обратного вызова, не является статичной.

Я читал об этом, но мне кажется, что мне нужно знать класс myObj заранее.Но я не уверен, как это сделать.Как я могу справиться с этим?Возможно ли это в C ++?

Это то, что я имею в виду, но, безусловно, неправильно.

class MyClassWithCallback{
public
    void *targetObj;
    void (*callback)(int number);
    void setCallback(void *myObj, void(*callbackPtr)(int number)){
        targetObj = myObj;
        callback = callbackPtr;
    };
    void callCallback(int a){
        (myObj)->ptr(a);
    };
};
class Target{
public
    int res;
    void doSomething(int a){//so something here. This is gonna be the callback function};        
};

int main(){
    Target myTarget;
    MyClassWithCallback myCaller;
    myCaller.setCallback((void *)&myTarget, &doSomething);

}

Я ценю любую помощь.

Спасибо.

ОБНОВЛЕНИЕ Большинство из вас сказали «Наблюдение и делегирование», что ж, это именно то, что я ищу, я своего рода парень из Objective-C / Cocoa.Моя текущая реализация использует интерфейсы с виртуальными функциями.Просто я подумал, что было бы «умнее» просто передать объект и указатель на функцию-член (например, boost!) Вместо определения интерфейса.Но кажется, что все согласны с тем, что интерфейсы - самый простой способ, верно?Хорошая идея, похоже, Boost (при условии, что он установлен)

Ответы [ 6 ]

16 голосов
/ 01 августа 2010

Лучшее решение, используйте boost::function с boost::bind, или если ваш компилятор поддерживает tr1 / c ++ 0x, используйте std::tr1::function и std::tr1::bind.

Так что это становится так просто, как:

boost::function<void()> callback;
Target myTarget;
callback=boost::bind(&Target::doSomething,&myTarget);

callback(); // calls the function

И ваш обратный вызов set становится:

class MyClassWithCallback{
public:
  void setCallback(boost::function<void()> const &cb)
  {
     callback_ = cb;
  }
  void call_it() { callback_(); }
private:
  boost::function<void()> callback_;
};

В противном случае вам нужно реализовать некоторый абстрактный класс

struct callback { 
 virtual void call() = 0;
 virtual ~callback() {}
};

struct TargetCallback {
 virtual void call() { ((*self).*member)()); }
 void (Target::*member)();
 Target *self;
 TargetCallback(void (Target::*m)(),Target *p) : 
       member(m),
       self(p)
 {}
};

И затем использовать:

myCaller.setCallback(new TargetCallback(&Target::doSomething,&myTarget));

Когда ваш класс изменяется на:

class MyClassWithCallback{
public:
  void setCallback(callback *cb)
  {
     callback_.reset(cb);
  }
  void call_it() { callback_->call(); }
private:
  std::auto_ptr<callback> callback_;
};

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

8 голосов
/ 01 августа 2010

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

например. (псевдокод)

struct myinterface
{
  void doSomething()=0;
};

class Target : public myinterface { ..implement doSomething... };

и

myinterface *targetObj; 
void setCallback(myinterface *myObj){
    targetObj = myObj;
};

делает обратный вызов

targetObj->doSomething();

настройка:

Target myTarget;
MyClassWithCallback myCaller;
myCaller.setCallback(myTarget);
5 голосов
/ 01 августа 2010

Шаблон проектирования Observer , кажется, то, что вы ищете.

4 голосов
/ 01 августа 2010

У вас есть несколько основных опций:

1) Укажите, какой класс будет использовать обратный вызов, чтобы указатель объекта и типы указателей на функции-члены были известны и могли использоваться в вызывающей программе. Класс может иметь несколько функций-членов с одной и той же сигнатурой, которую вы можете выбрать, но ваши параметры весьма ограничены.

Одна вещь, которую вы сделали неправильно в своем коде, заключается в том, что указатели на функции-члены и указатели свободных функций в C ++ не совпадают и не являются совместимыми типами. Ваша функция регистрации обратного вызова принимает указатель на функцию, но вы пытаетесь передать ей указатель на функцию-член. Не положено. Кроме того, тип объекта «this» является частью типа указателя на функцию-член, поэтому в C ++ нет такой вещи, как «указатель на любую функцию-член, которая принимает целое число и возвращает void». Это должен быть «указатель на любую функцию-член объекта Target , которая принимает целое число и возвращает void». Отсюда и ограниченные возможности.

2) Определить чисто виртуальную функцию в классе интерфейса. Поэтому любой класс, который хочет получить обратный вызов, может наследовать от класса интерфейса. Благодаря множественному наследованию это не мешает остальной иерархии классов. Это почти то же самое, что и определение интерфейса в Java.

3) Использовать функцию, не являющуюся членом для обратного вызова. Для каждого класса, который хочет его использовать, вы пишете небольшую функцию-заглушку, которая берет указатель на объект и вызывает для него правильную функцию-член. Так что в вашем случае у вас будет:

dosomething_stub(void *obj, int a) {
    ((Target *)obj)->doSomething(a);
}

4) Использовать шаблоны:

template<typename CB> class MyClassWithCallback {
    CB *callback;
 public:
    void setCallback(CB &cb) { callback = &cb; }
    void callCallback(int a) {
        callback(a);
    }
};

class Target {
    void operator()(int a) { /* do something; */ }
};

int main() {
    Target t;
    MyClassWithCallback<T> caller;
    caller.setCallback(t);
}

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

3 голосов
/ 01 августа 2010

Также посмотрите на Наблюдательский паттерн и сигналы и слоты .Это распространяется на нескольких подписчиков.

2 голосов
/ 01 августа 2010

В C ++ указатели на методы класса практически не используются.То, что вы вызывали - это делегаты и их использование не рекомендуется.Вместо них вы должны использовать виртуальные функции и абстрактные классы.Тем не менее, C ++ не был бы так привязан ко мне, если бы он не поддерживал совершенно разные концепции программирования.Если вам все еще нужны делегаты, вам следует обратить внимание на «функционал повышения» (часть C + + 0 x), он позволяет указатели на методы классов независимо от имени класса.Кроме того, в C ++ Builder есть тип __closure - реализация делегата на уровне компилятора.

PS Извините за плохой английский ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...