Как реализовать обратный вызов в С++?
Я хочу реализовать класс в С++, который имеет обратный вызов.
Итак, мне нужен метод, который имеет 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 частью какой-то большой старой структуры - если это так, то это может быть невозможно (если быть точным: может потребоваться еще несколько трюков, таких как класс шаблона, который наследуется от не- класс шаблона, имеющий функцию виртуального члена), потому что вы не можете создать экземпляр всей структуры один раз для каждого получателя обратного вызова.
Ответ 5
Кроме того, посмотрите на шаблон наблюдателя и сигналы и слоты. Это распространяется на несколько подписчиков.
Ответ 6
В С++ методы указателей на класс вряд ли используются. Тот факт, что вы вызвали - это делегаты, и их использование не рекомендуется. Вместо них вы должны использовать виртуальные функции и абстрактные классы.
Однако С++ не был бы так увлечен мной, если бы он не поддерживал совершенно разные концепции программирования. Если вам все еще нужны делегаты, вы должны посмотреть на "boost function" (часть C + +0 x), он позволяет указателям на методы классов независимо от имени класса. Кроме того, в С++ Builder есть тип __closure - реализация делегата на уровне компилятора.
P.S. Извините за плохой английский...