Люди обычно используют один из нескольких шаблонов:
Наследование. То есть вы определяете абстрактный класс, который содержит обратный вызов. Затем вы берете указатель / ссылку на него. Это означает, что каждый может наследовать и предоставлять этот обратный вызов.
class Foo {
virtual void MyCallback(...) = 0;
virtual ~Foo();
};
class Base {
std::auto_ptr<Foo> ptr;
void something(...) {
ptr->MyCallback(...);
}
Base& SetCallback(Foo* newfoo) { ptr = newfoo; return *this; }
Foo* GetCallback() { return ptr; }
};
Наследование снова. То есть ваш корневой класс является абстрактным, и пользователь наследует его и определяет обратные вызовы, вместо того, чтобы иметь конкретный класс и выделенные объекты обратного вызова.
class Foo {
virtual void MyCallback(...) = 0;
...
};
class RealFoo : Foo {
virtual void MyCallback(...) { ... }
};
Еще более наследственное - статическое. Таким образом, вы можете использовать шаблоны для изменения поведения объекта. Он похож на второй вариант, но работает во время компиляции, а не во время выполнения, что может привести к различным преимуществам и недостаткам, в зависимости от контекста.
template<typename T> class Foo {
void MyCallback(...) {
T::MyCallback(...);
}
};
class RealFoo : Foo<RealFoo> {
void MyCallback(...) {
...
}
};
Вы можете взять и использовать указатели на функции-члены или обычные указатели на функции
class Foo {
void (*callback)(...);
void something(...) { callback(...); }
Foo& SetCallback( void(*newcallback)(...) ) { callback = newcallback; return *this; }
void (*)(...) GetCallback() { return callback; }
};
Есть функциональные объекты - они перегружают оператор (). Вы захотите использовать или написать функциональную обертку - в настоящее время предоставленную в std :: / boost :: function, но я также продемонстрирую здесь простую. Это похоже на первую концепцию, но скрывает реализацию и принимает множество других решений. Лично я обычно использую это как свой метод обратного вызова.
class Foo {
virtual ... Call(...) = 0;
virtual ~Foo();
};
class Base {
std::auto_ptr<Foo> callback;
template<typename T> Base& SetCallback(T t) {
struct NewFoo : Foo {
T t;
NewFoo(T newt) : t(newt) {}
... Call(...) { return t(...); }
};
callback = new NewFoo<T>(t);
return this;
}
Foo* GetCallback() { return callback; }
void dosomething() { callback->Call(...); }
};
Правильное решение в основном зависит от контекста. Если вам нужно предоставить API в стиле C, тогда указатели на функции - единственный путь (помните void * для пользовательских аргументов). Если вам нужно изменить во время выполнения (например, представить код в предварительно скомпилированной библиотеке), тогда статическое наследование здесь не может быть использовано.
Просто короткое замечание: я вручную написал этот код, чтобы он не был идеальным (например, модификаторы доступа для функций и т. Д.) И мог содержать пару ошибок. Это пример.