В этой статье я кратко опишу готовое решение по вопросу о том, как в C++ / Qt написать функцию обратного вызова (callback-функцию).
Задача: есть некий класс Airplane (самолетик). Нужно сделать этому классу интерфейсный метод, через который можно указать, какую внешнюю функцию использовать для управления поведением самолетика. Затем надо создать объект класса Airplane, и задать ему функцию поведения.
Пусть метод, через который устанавливается функция поведения называется setCallbackFunc. Этот метод должен иметь всего один параметр - указатель на функцию, который он должен запомнить. Тогда интерфейс класса самолетика (h-файл) может выглядеть так:
class Airplane : public QObject
{
Airplane(QObject *pobj=0);
virtual ~Airplane();
void setCallbackFunc(void (*func)(QObject *airplane,
int &x,
int &y));
...
private:
int x;
int y;
// Указатель в котором запоминается
// указатель на функцию поведения самолетика
void (*callbackFunc)(QObject *airplane, int &x, int &y));
};
Вопрос: сколько параметров мы видим в прототипе метода setCallbackFunc? Для тех, кто слабо понимает жуткий синтаксис C++, скажу, что правильный ответ - один. Почему так, будет объяснено чуть ниже. Теперь непосредственно реализация метода, который устанавливает функцию обратного вызова:
void Airplane::setCallbackFunc( void (*func)(QObject *airplane,
int &x,
int &y) )
{
// Эапоминается переданный указатель
callbackFunc=func;
}
Для управления самолетиком будем использовать обычную функцию (хотя, вместо обычной функции можно использовать метод какого-нибудь класса).
Как видно из вышеприведенного кода, функция, которая должна управлять поведением самолетика, принимает указатель на объект, который ее вызвал (переменная *airplane). Можно обойтись и без этого указателя, но в дальнейшем он может пригодиться для вызова каких-то методов объекта самолетика. Так же эта функция принимает текущие координаты x и y самолетика по ссыке, чтобы функция могла эти координаты изменять.
Важный момент для понимания. Найдите в вышеприведенном коде конструкцию:
void (*func)(QObject *airplane,
int &x,
int &y)
Эта монструозная конструкция ни что иное, как указатель на функцию. Причем не на абы какую функцию, а только на ту, которая возвращает тип void и принимает три аргумента с типами QObject*, int&, int&. Имя этого указателя - func.
Функция управления самолетиком может выглядеть так:
Прототип в h-файле:
static void fly(QObject *airplane, int &x, int &y);
Обратите внимание, что прототип функции fly() описывается как static. Это важно, так как только статические функции (или методы классов) могут работать как callback-функции. Если забыть указать ключевое слово static, то в момент компиляции будет ошибка вида:
error: no matching function for call to ‘Airplane::setCallbackFunc(<unresolved overloaded function type>)’
candidates are: void Airplane::setCallbackFunc(void (*)(QObject*, int&, int&))
Реализация функции управления самолетиком:
void fly(QObject *airplane, int &x, int &y)
{
...
x=x+y;
y=y+1;
}
С такой реализацией самолетик полетит по мягкой параболе. Обратите внимание, что в реализации ключевое слово static не указывается. Это особенность синтаксиса C++: ключевое слово static нужно указывать только в прототипе.
Далее возникает вопрос: а как вызывать эту функцию обратного вызова? А очень просто! Предположим, что у класса Airplane есть метод update(), и в нем нужно сделать вызов нашей callback-функции. Код этого метода может выглядеть так:
void Airplane::update()
{
// Вызов функции управления поведением самолетика
// qobject_cast<QObject *>(this) - это указатель
// на текущий объект самолетика
// x и y - текущие координаты самолетика
callbackFunc(qobject_cast<QObject *>(this), x, y);
...
redraw();
}
Теперь последний штрих. Создаем объект самолетика, и задаем ему функцию поведения:
Airplane mig29;
mig29.setCallbackFunc(fly);
Все! Если теперь постоянно вызывать метод mig29.update(), то координаты самолетика будут меняться согласно алгоритму, заданному в функции fly().