Как реализовать обратный вызов в С++?

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

Итак, мне нужен метод, который имеет 2 аргумента:

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

Условия:

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

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

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

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

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

}

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

Спасибо.

UPDATE Большинство из вас сказали "Наблюдение и Делегирование", хорошо, что я точно то, что я ищу, я своего рода Objective-C/Cocoa единомышленником. В моей текущей реализации используются интерфейсы с виртуальными функциями. Я просто подумал, что было бы "умнее" просто передать объект и указатель функции-члена (например, boost!) Вместо определения интерфейса. Но похоже, что все согласны с тем, что интерфейсы являются самым простым способом? Boost кажется хорошей идеей (предполагается, что установлен)

Ответ 1

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

Итак, он становится таким простым, как:

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

callback(); // calls the function

И ваш обратный вызов будет:

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 из некоторого абстрактного класса с помощью этого вызова.

Ответ 2

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

Ответ 3

Дизайн Observer кажется тем, что вы ищете.

Ответ 4

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

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

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

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

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

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

Ответ 6

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

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