Відповіді:
Примітка: Більшість відповідей охоплюють покажчики функцій, що є однією можливістю досягти логіки "зворотного виклику" в C ++, але на сьогоднішній день не найсприятливіший.
Як передзвонити є викликається (див далі вниз) прийнятий класом або функцією, використовуваним для настройки поточної логіки в залежності від цього зворотного виклику.
Однією з причин використання зворотних викликів є написання загального коду, який не залежить від логіки в викликаній функції і може бути повторно використаний з різними зворотними зворотами.
Багато функцій бібліотеки стандартних алгоритмів <algorithm>
використовують зворотні дзвінки. Наприклад, for_each
алгоритм застосовує одинаковий зворотний виклик до кожного елемента в діапазоні ітераторів:
template<class InputIt, class UnaryFunction>
UnaryFunction for_each(InputIt first, InputIt last, UnaryFunction f)
{
for (; first != last; ++first) {
f(*first);
}
return f;
}
який може бути використаний для збільшення та спочатку надрукування вектора, передаючи відповідні дзвінки, наприклад:
std::vector<double> v{ 1.0, 2.2, 4.0, 5.5, 7.2 };
double r = 4.0;
std::for_each(v.begin(), v.end(), [&](double & v) { v += r; });
std::for_each(v.begin(), v.end(), [](double v) { std::cout << v << " "; });
який друкує
5 6.2 8 9.5 11.2
Ще одне застосування зворотних викликів - повідомлення сповіщувачів про певні події, що забезпечує певну кількість статичної / гнучкої компіляції часу.
Особисто я використовую локальну бібліотеку оптимізації, яка використовує два різні зворотні виклики:
Таким чином, дизайнер бібліотеки не відповідає за прийняття рішення про те, що відбувається з інформацією, яка надається програмісту за допомогою зворотного виклику сповіщень, і він не повинен турбуватися про те, як фактично визначити значення функцій, оскільки вони забезпечуються логічним зворотним дзвінком. Правильне налаштування цих завдань є завданням, що належить користувачеві бібліотеки, і зберігає бібліотеку стрункою та загальною.
Крім того, зворотні виклики можуть включати динамічну поведінку під час виконання.
Уявіть собі якийсь клас ігрового двигуна, який має функцію, яка спрацьовує, кожного разу, коли користувачі натискають кнопку на його клавіатурі та набір функцій, які керують вашою ігровою поведінкою. За допомогою зворотних викликів ви можете (повторно) вирішити під час виконання, які дії будуть вжиті.
void player_jump();
void player_crouch();
class game_core
{
std::array<void(*)(), total_num_keys> actions;
//
void key_pressed(unsigned key_id)
{
if(actions[key_id]) actions[key_id]();
}
// update keybind from menu
void update_keybind(unsigned key_id, void(*new_action)())
{
actions[key_id] = new_action;
}
};
Тут функція key_pressed
використовує зворотні виклики, які зберігаються, actions
щоб отримати бажану поведінку при натисканні певної клавіші. Якщо гравець вирішить змінити кнопку для стрибків, двигун може зателефонувати
game_core_instance.update_keybind(newly_selected_key, &player_jump);
і, таким чином, змінити поведінку дзвінка на key_pressed
( до якого дзвонить player_jump
) після натискання цієї кнопки наступного разу.
Дивіться поняття C ++: Телефонуйте за допомогою cppreference для отримання більш офіційного опису.
Функція зворотного дзвінка може бути реалізована декількома способами в C ++ (11), оскільки декілька різних речей можуть бути зателефоновані * :
std::function
об’єктиoperator()
)* Примітка: Вказівник на членів даних також може дзвонити, але функція взагалі не викликається.
Примітка: Станом на C ++ 17 f(...)
може бути записаний подібний виклик , std::invoke(f, ...)
який також обробляє вказівник на регістр учасника.
Покажчик функції - це найпростіший (з точки зору загальності; з точки зору читабельності, можливо, найгірший) тип зворотного виклику.
Будемо мати просту функцію foo
:
int foo (int x) { return 2+x; }
Тип покажчика функції має позначення
return_type (*)(parameter_type_1, parameter_type_2, parameter_type_3)
// i.e. a pointer to foo has the type:
int (*)(int)
де буде виглядати названий тип вказівника функції
return_type (* name) (parameter_type_1, parameter_type_2, parameter_type_3)
// i.e. f_int_t is a type: function pointer taking one int argument, returning int
typedef int (*f_int_t) (int);
// foo_p is a pointer to function taking int returning int
// initialized by pointer to function foo taking int returning int
int (* foo_p)(int) = &foo;
// can alternatively be written as
f_int_t foo_p = &foo;
using
Декларація дає нам можливість зробити речі трохи більш зручним для читання, так як typedef
для f_int_t
також можна записати в вигляді:
using f_int_t = int(*)(int);
Де (принаймні для мене) зрозуміліше, що f_int_t
псевдонім нового типу і розпізнавання типу вказівника функції також простіше
І оголошення функції з використанням зворотного виклику типу вказівника функції буде:
// foobar having a callback argument named moo of type
// pointer to function returning int taking int as its argument
int foobar (int x, int (*moo)(int));
// if f_int is the function pointer typedef from above we can also write foobar as:
int foobar (int x, f_int_t moo);
Позначення виклику випливає з простого синтаксису виклику функції:
int foobar (int x, int (*moo)(int))
{
return x + moo(x); // function pointer moo called using argument x
}
// analog
int foobar (int x, f_int_t moo)
{
return x + moo(x); // function pointer moo called using argument x
}
Функцію зворотного виклику, яка приймає функціональний покажчик, можна викликати за допомогою функціональних покажчиків.
Використання функції, яка приймає функцію зворотного дзвінка вказівника, досить проста:
int a = 5;
int b = foobar(a, foo); // call foobar with pointer to foo as callback
// can also be
int b = foobar(a, &foo); // call foobar with pointer to foo as callback
Функція може бути записана, яка не покладається на те, як працює зворотний виклик:
void tranform_every_int(int * v, unsigned n, int (*fp)(int))
{
for (unsigned i = 0; i < n; ++i)
{
v[i] = fp(v[i]);
}
}
де можливі зворотні дзвінки
int double_int(int x) { return 2*x; }
int square_int(int x) { return x*x; }
використовується як
int a[5] = {1, 2, 3, 4, 5};
tranform_every_int(&a[0], 5, double_int);
// now a == {2, 4, 6, 8, 10};
tranform_every_int(&a[0], 5, square_int);
// now a == {4, 16, 36, 64, 100};
Функція вказівника на член-член (деякого класу C
) - це особливий тип (і навіть більш складний) вказівник функції, який вимагає для роботи об'єкта типу C
.
struct C
{
int y;
int foo(int x) const { return x+y; }
};
Показник на тип функції члена для деякого класу T
має позначення
// can have more or less parameters
return_type (T::*)(parameter_type_1, parameter_type_2, parameter_type_3)
// i.e. a pointer to C::foo has the type
int (C::*) (int)
де названий вказівник на функцію-члена буде аналогічно функції вказівника - виглядатиме так:
return_type (T::* name) (parameter_type_1, parameter_type_2, parameter_type_3)
// i.e. a type `f_C_int` representing a pointer to member function of `C`
// taking int returning int is:
typedef int (C::* f_C_int_t) (int x);
// The type of C_foo_p is a pointer to member function of C taking int returning int
// Its value is initialized by a pointer to foo of C
int (C::* C_foo_p)(int) = &C::foo;
// which can also be written using the typedef:
f_C_int_t C_foo_p = &C::foo;
Приклад: Оголошення функції, що приймає покажчик на зворотний виклик функції члена, як один із його аргументів:
// C_foobar having an argument named moo of type pointer to member function of C
// where the callback returns int taking int as its argument
// also needs an object of type c
int C_foobar (int x, C const &c, int (C::*moo)(int));
// can equivalently declared using the typedef above:
int C_foobar (int x, C const &c, f_C_int_t moo);
Функція вказівника на член-член C
може бути викликана стосовно типу об'єкта C
, використовуючи операції доступу членів на відміненому вказівнику.
Примітка: необхідні парентези!
int C_foobar (int x, C const &c, int (C::*moo)(int))
{
return x + (c.*moo)(x); // function pointer moo called for object c using argument x
}
// analog
int C_foobar (int x, C const &c, f_C_int_t moo)
{
return x + (c.*moo)(x); // function pointer moo called for object c using argument x
}
Примітка: Якщо вказівник на C
доступний, синтаксис еквівалентний (де вказівник також C
повинен бути відмінений):
int C_foobar_2 (int x, C const * c, int (C::*meow)(int))
{
if (!c) return x;
// function pointer meow called for object *c using argument x
return x + ((*c).*meow)(x);
}
// or equivalent:
int C_foobar_2 (int x, C const * c, int (C::*meow)(int))
{
if (!c) return x;
// function pointer meow called for object *c using argument x
return x + (c->*meow)(x);
}
Функцію зворотного дзвінка, що приймає вказівник функції члена класу, T
можна викликати, використовуючи вказівник функції класу класу T
.
Використання функції, яка приймає покажчик на функцію зворотного виклику учасника, - це аналогія функції покажчиків - також просто:
C my_c{2}; // aggregate initialization
int a = 5;
int b = C_foobar(a, my_c, &C::foo); // call C_foobar with pointer to foo as its callback
std::function
об'єкти (заголовок <functional>
)std::function
Клас є поліморфною функцією обгорткою для зберігання, копіювання або запускайте викликаються об'єкти.
std::function
позначень об'єкта / типуТип std::function
об'єкта, що зберігає дзвінок, виглядає так:
std::function<return_type(parameter_type_1, parameter_type_2, parameter_type_3)>
// i.e. using the above function declaration of foo:
std::function<int(int)> stdf_foo = &foo;
// or C::foo:
std::function<int(const C&, int)> stdf_C_foo = &C::foo;
Клас std::function
має operator()
певний , який може бути використаний для виклику своєї мети.
int stdf_foobar (int x, std::function<int(int)> moo)
{
return x + moo(x); // std::function moo called
}
// or
int stdf_C_foobar (int x, C const &c, std::function<int(C const &, int)> moo)
{
return x + moo(c, x); // std::function moo called using c and x
}
Зворотний std::function
виклик є більш загальним, ніж покажчики функцій або вказівник на функцію члена, оскільки різні типи можуть передаватися та неявно перетворюватися в std::function
об'єкт.
3.3.1 Показники та покажчики функцій членів
Покажчик функції
int a = 2;
int b = stdf_foobar(a, &foo);
// b == 6 ( 2 + (2+2) )
або вказівник на функцію члена
int a = 2;
C my_c{7}; // aggregate initialization
int b = stdf_C_foobar(a, c, &C::foo);
// b == 11 == ( 2 + (7+2) )
може бути використано.
3.3.2 Лямбда-вирази
Неназване закриття від лямбда-виразу може зберігатися в std::function
об’єкті:
int a = 2;
int c = 3;
int b = stdf_foobar(a, [c](int x) -> int { return 7+c*x; });
// b == 15 == a + (7*c*a) == 2 + (7+3*2)
3.3.3 std::bind
вирази
Результат std::bind
виразу можна передавати. Наприклад, прив’язуючи параметри до виклику функції вказівника:
int foo_2 (int x, int y) { return 9*x + y; }
using std::placeholders::_1;
int a = 2;
int b = stdf_foobar(a, std::bind(foo_2, _1, 3));
// b == 23 == 2 + ( 9*2 + 3 )
int c = stdf_foobar(a, std::bind(foo_2, 5, _1));
// c == 49 == 2 + ( 9*5 + 2 )
Де також об'єкти можуть бути пов'язані як об'єкт для виклику вказівника на функції члена:
int a = 2;
C const my_c{7}; // aggregate initialization
int b = stdf_foobar(a, std::bind(&C::foo, my_c, _1));
// b == 1 == 2 + ( 2 + 7 )
3.3.4 Об'єкти функцій
Об'єкти класів, що мають належну operator()
перевантаження, також можуть зберігатися всередині std::function
об'єкта.
struct Meow
{
int y = 0;
Meow(int y_) : y(y_) {}
int operator()(int x) { return y * x; }
};
int a = 11;
int b = stdf_foobar(a, Meow{8});
// b == 99 == 11 + ( 8 * 11 )
Зміна прикладу вказівника функції на використання std::function
void stdf_tranform_every_int(int * v, unsigned n, std::function<int(int)> fp)
{
for (unsigned i = 0; i < n; ++i)
{
v[i] = fp(v[i]);
}
}
надає набагато більше корисності цій функції, оскільки (див. 3.3) ми маємо більше можливостей її використовувати:
// using function pointer still possible
int a[5] = {1, 2, 3, 4, 5};
stdf_tranform_every_int(&a[0], 5, double_int);
// now a == {2, 4, 6, 8, 10};
// use it without having to write another function by using a lambda
stdf_tranform_every_int(&a[0], 5, [](int x) -> int { return x/2; });
// now a == {1, 2, 3, 4, 5}; again
// use std::bind :
int nine_x_and_y (int x, int y) { return 9*x + y; }
using std::placeholders::_1;
// calls nine_x_and_y for every int in a with y being 4 every time
stdf_tranform_every_int(&a[0], 5, std::bind(nine_x_and_y, _1, 4));
// now a == {13, 22, 31, 40, 49};
Використовуючи шаблони, код, що викликає зворотний виклик, може бути навіть більш загальним, ніж використання std::function
об'єктів.
Зауважте, що шаблони є функцією часу компіляції та є інструментом проектування для поліморфізму часу компіляції. Якщо динамічна поведінка часу виконання повинна бути досягнута за допомогою зворотних зворотних дзвінків, шаблони допоможуть, але вони не будуть індукувати динаміку виконання.
Узагальнення, тобто std_ftransform_every_int
код зверху ще більше можна досягти за допомогою шаблонів:
template<class R, class T>
void stdf_transform_every_int_templ(int * v,
unsigned const n, std::function<R(T)> fp)
{
for (unsigned i = 0; i < n; ++i)
{
v[i] = fp(v[i]);
}
}
з ще більш загальним (а також найпростішим) синтаксисом для типу зворотного виклику, що є простим, виведеним шаблонним аргументом:
template<class F>
void transform_every_int_templ(int * v,
unsigned const n, F f)
{
std::cout << "transform_every_int_templ<"
<< type_name<F>() << ">\n";
for (unsigned i = 0; i < n; ++i)
{
v[i] = f(v[i]);
}
}
Примітка: Включений вихід видає ім'я типу, виведене для шаблонного типу F
. Реалізація type_name
надана в кінці цієї посади.
Найбільш загальна реалізація для одинарного перетворення діапазону є частиною стандартної бібліотеки, а саме std::transform
, яка також шаблонна стосовно ітераційних типів.
template<class InputIt, class OutputIt, class UnaryOperation>
OutputIt transform(InputIt first1, InputIt last1, OutputIt d_first,
UnaryOperation unary_op)
{
while (first1 != last1) {
*d_first++ = unary_op(*first1++);
}
return d_first;
}
Сумісні типи шаблонного std::function
методу зворотного виклику stdf_transform_every_int_templ
ідентичні вищезгаданим типам (див. 3.4).
Однак, використовуючи шаблонну версію, підпис використаного зворотного дзвінка може дещо змінити:
// Let
int foo (int x) { return 2+x; }
int muh (int const &x) { return 3+x; }
int & woof (int &x) { x *= 4; return x; }
int a[5] = {1, 2, 3, 4, 5};
stdf_transform_every_int_templ<int,int>(&a[0], 5, &foo);
// a == {3, 4, 5, 6, 7}
stdf_transform_every_int_templ<int, int const &>(&a[0], 5, &muh);
// a == {6, 7, 8, 9, 10}
stdf_transform_every_int_templ<int, int &>(&a[0], 5, &woof);
Примітка: std_ftransform_every_int
(не шаблонна версія; див. Вище) працює з, foo
але не використовується muh
.
// Let
void print_int(int * p, unsigned const n)
{
bool f{ true };
for (unsigned i = 0; i < n; ++i)
{
std::cout << (f ? "" : " ") << p[i];
f = false;
}
std::cout << "\n";
}
Простий шаблоновий параметр transform_every_int_templ
може бути всіх можливих типів дзвінка.
int a[5] = { 1, 2, 3, 4, 5 };
print_int(a, 5);
transform_every_int_templ(&a[0], 5, foo);
print_int(a, 5);
transform_every_int_templ(&a[0], 5, muh);
print_int(a, 5);
transform_every_int_templ(&a[0], 5, woof);
print_int(a, 5);
transform_every_int_templ(&a[0], 5, [](int x) -> int { return x + x + x; });
print_int(a, 5);
transform_every_int_templ(&a[0], 5, Meow{ 4 });
print_int(a, 5);
using std::placeholders::_1;
transform_every_int_templ(&a[0], 5, std::bind(foo_2, _1, 3));
print_int(a, 5);
transform_every_int_templ(&a[0], 5, std::function<int(int)>{&foo});
print_int(a, 5);
Наведений вище код друкує:
1 2 3 4 5
transform_every_int_templ <int(*)(int)>
3 4 5 6 7
transform_every_int_templ <int(*)(int&)>
6 8 10 12 14
transform_every_int_templ <int& (*)(int&)>
9 11 13 15 17
transform_every_int_templ <main::{lambda(int)#1} >
27 33 39 45 51
transform_every_int_templ <Meow>
108 132 156 180 204
transform_every_int_templ <std::_Bind<int(*(std::_Placeholder<1>, int))(int, int)>>
975 1191 1407 1623 1839
transform_every_int_templ <std::function<int(int)>>
977 1193 1409 1625 1841
type_name
реалізація, що використовується вище#include <type_traits>
#include <typeinfo>
#include <string>
#include <memory>
#include <cxxabi.h>
template <class T>
std::string type_name()
{
typedef typename std::remove_reference<T>::type TR;
std::unique_ptr<char, void(*)(void*)> own
(abi::__cxa_demangle(typeid(TR).name(), nullptr,
nullptr, nullptr), std::free);
std::string r = own != nullptr?own.get():typeid(TR).name();
if (std::is_const<TR>::value)
r += " const";
if (std::is_volatile<TR>::value)
r += " volatile";
if (std::is_lvalue_reference<T>::value)
r += " &";
else if (std::is_rvalue_reference<T>::value)
r += " &&";
return r;
}
int b = foobar(a, foo); // call foobar with pointer to foo as callback
, це друкарське право? foo
повинен бути вказівником для цього для роботи AFAIK.
[conv.func]
стандарту C ++ 11 говориться: " Значення функції типу T може бути перетворене в первісне значення типу" покажчик на T. " Результатом є вказівник на функцію. "Це стандартне перетворення і як таке відбувається неявно. Тут можна, звичайно, використати покажчик функції.
Існує також спосіб C зворотного виклику: функціональні вказівники
//Define a type for the callback signature,
//it is not necessary, but makes life easier
//Function pointer called CallbackType that takes a float
//and returns an int
typedef int (*CallbackType)(float);
void DoWork(CallbackType callback)
{
float variable = 0.0f;
//Do calculations
//Call the callback with the variable, and retrieve the
//result
int result = callback(variable);
//Do something with the result
}
int SomeCallback(float variable)
{
int result;
//Interpret variable
return result;
}
int main(int argc, char ** argv)
{
//Pass in SomeCallback to the DoWork
DoWork(&SomeCallback);
}
Тепер, якщо ви хочете передати методи класу як зворотні дзвінки, декларації до цих функціональних покажчиків мають більш складні декларації, наприклад:
//Declaration:
typedef int (ClassName::*CallbackType)(float);
//This method performs work using an object instance
void DoWorkObject(CallbackType callback)
{
//Class instance to invoke it through
ClassName objectInstance;
//Invocation
int result = (objectInstance.*callback)(1.0f);
}
//This method performs work using an object pointer
void DoWorkPointer(CallbackType callback)
{
//Class pointer to invoke it through
ClassName * pointerInstance;
//Invocation
int result = (pointerInstance->*callback)(1.0f);
}
int main(int argc, char ** argv)
{
//Pass in SomeCallback to the DoWork
DoWorkObject(&ClassName::Method);
DoWorkPointer(&ClassName::Method);
}
typedef
використовуючи тип зворотного виклику? Чи можливо це навіть?
typedef
це просто синтаксичний цукор, щоб зробити його більш читабельним. Без typedef
визначення DoWorkObject для покажчиків на функції буде: void DoWorkObject(int (*callback)(float))
. Для вказівників-членів буде:void DoWorkObject(int (ClassName::*callback)(float))
Скотт Мейєрс дає хороший приклад:
class GameCharacter;
int defaultHealthCalc(const GameCharacter& gc);
class GameCharacter
{
public:
typedef std::function<int (const GameCharacter&)> HealthCalcFunc;
explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc)
: healthFunc(hcf)
{ }
int healthValue() const { return healthFunc(*this); }
private:
HealthCalcFunc healthFunc;
};
Я думаю, що приклад говорить про це все.
std::function<>
це "сучасний" спосіб запису зворотних дзвінків на C ++.
Функція зворотного виклику - це метод, який передається у звичайну процедуру і викликається в якийсь момент рутиною, до якої він передається.
Це дуже корисно для створення програмного забезпечення для багаторазового використання. Наприклад, багато API API операційної системи (наприклад, API Windows) активно використовують зворотні дзвінки.
Наприклад, якщо ви хотіли працювати з файлами в папці - ви можете викликати функцію API за допомогою власного розпорядку, і ваша рутина запускається один раз на файл у вказаній папці. Це дозволяє API бути дуже гнучким.
Прийнята відповідь дуже корисна і досить вичерпна. Однак ОП констатує
Я хотів би побачити простий приклад написання функції зворотного дзвінка.
Отже, ви переходите, від C ++ 11 у вас є, std::function
тому немає необхідності у функціональних покажчиках та подібних матеріалах:
#include <functional>
#include <string>
#include <iostream>
void print_hashes(std::function<int (const std::string&)> hash_calculator) {
std::string strings_to_hash[] = {"you", "saved", "my", "day"};
for(auto s : strings_to_hash)
std::cout << s << ":" << hash_calculator(s) << std::endl;
}
int main() {
print_hashes( [](const std::string& str) { /** lambda expression */
int result = 0;
for (int i = 0; i < str.length(); i++)
result += pow(31, i) * str.at(i);
return result;
});
return 0;
}
Цей приклад до речі якимось реальним, тому що ви хочете викликати функцію print_hashes
з різними реалізаціями хеш-функцій, для цього я запропонував просту. Він отримує рядок, повертає int (хеш-значення поданої рядка), і все, що вам потрібно запам’ятати з частини синтаксису, - це те, std::function<int (const std::string&)>
що описує таку функцію як вхідний аргумент функції, яка буде викликати її.
У C ++ немає явного поняття функції зворотного виклику. Механізми зворотного виклику часто реалізовуються за допомогою функціональних покажчиків, об'єктів функтора або об'єктів зворотного виклику. Програмісти повинні чітко розробити та реалізувати функцію зворотного виклику.
Редагувати на основі відгуків:
Незважаючи на негативний відгук, який отримала ця відповідь, це не є помилковим. Я спробую зробити кращу роботу, пояснюючи, звідки я родом.
C і C ++ мають все необхідне для реалізації функцій зворотного дзвінка. Найпоширеніший і тривіальний спосіб реалізації функції зворотного виклику - передача покажчика функції як аргументу функції.
Однак функції зворотного дзвінка та покажчики функцій не є синонімами. Покажчик функції - це мовний механізм, тоді як функція зворотного виклику - це смислове поняття. Покажчики функцій - не єдиний спосіб реалізувати функцію зворотного дзвінка - ви також можете використовувати функтори та навіть віртуальні функції різноманітних садів. Те, що робить функцію зворотного виклику функції, це не механізм, який використовується для ідентифікації та виклику функції, а контекст та семантика виклику. Скажіть, що щось є функцією зворотного виклику, має на увазі більшу, ніж зазвичай, поділ між функцією виклику та конкретною функцією, що викликається, більш слабкою концептуальною зв'язкою між абонентом і позивачем, при цьому абонент має чіткий контроль над тим, що викликається.
Наприклад, документація .NET для IFormatProvider говорить про те, що "GetFormat - метод зворотного виклику" , навіть якщо це лише метод інтерфейсу, який виконується за допомогою заводу. Я не думаю, що хтось заперечуватиме, що всі виклики віртуальних методів є функціями зворотного виклику. Що робить GetFormat методом зворотного виклику не механікою того, як він передається чи викликається, а семантикою того, хто телефонує, що викликає, який метод GetFormat об'єкта буде викликатися.
Деякі мови містять функції з явною семантикою зворотного виклику, як правило, пов'язані з подіями та обробкою подій. Наприклад, C # має тип події із синтаксисом та семантикою, явно розробленими навколо концепції зворотних викликів. Visual Basic має Handles положення, в якому чітко декларує метод є функцією зворотного виклику , в той час як абстрагування від концепції делегатів або покажчиків на функції. У цих випадках смислове поняття зворотного дзвінка інтегрується в саму мову.
З іншого боку, C і C ++ не вкладають семантичну концепцію функцій зворотного виклику майже так само явно. Механізми є, інтегрованої семантики немає. Ви можете реалізувати функції зворотного виклику просто чудово, але щоб отримати щось більш складне, що включає явну семантику зворотного виклику, ви повинні побудувати його над тим, що надає C ++, наприклад, те, що Qt робив зі своїми сигналами та слотами .
У двох словах, у C ++ є те, що вам потрібно для реалізації зворотних викликів, часто досить легко і тривіально, використовуючи функціональні покажчики. У ньому немає ключових слів і функцій, семантика яких є специфічною для зворотних викликів, таких як підвищення , випромінювання , ручки , подія + = і т. Д. Якщо ви переходите з мови з тими елементами, підтримка нативної зворотного дзвінка в C ++ буде відчувати себе кастрированим.
Функції зворотного дзвінка є частиною стандарту C, тому також є частиною C ++. Але якщо ви працюєте з C ++, я б запропонував замість цього використовувати шаблон спостерігача : http://en.wikipedia.org/wiki/Observer_pattern
Див. Вищевикладене визначення, де зазначено, що функція зворотного виклику передається іншій функції, а в якийсь момент вона викликається.
У C ++ бажано мати функції зворотного виклику виклику методу класів. Після цього у вас є доступ до даних учасників. Якщо ви використовуєте спосіб визначення зворотного дзвінка C, вам доведеться вказати його на статичну функцію члена. Це не дуже бажано.
Ось як можна використовувати зворотні дзвінки в C ++. Припустимо 4 файли. Пара файлів .CPP / .H для кожного класу. Клас C1 - це клас із методом, який ми хочемо відкликати. C2 повертається до методу C1. У цьому прикладі функція зворотного виклику приймає 1 параметр, який я додав заради читачів. У прикладі не відображаються будь-які об'єкти, які використовуються при використанні. Один випадок використання для цієї реалізації - коли у вас є один клас, який читає і зберігає дані у тимчасовий простір, а інший, який публікує дані. За допомогою функції зворотного дзвінка для кожного рядка даних, прочитаних, зворотний дзвінок може потім обробляти його. Цей прийом вирізає накладні витрати на необхідний тимчасовий простір. Це особливо корисно для SQL запитів, які повертають велику кількість даних, які потім мають бути оброблені.
/////////////////////////////////////////////////////////////////////
// C1 H file
class C1
{
public:
C1() {};
~C1() {};
void CALLBACK F1(int i);
};
/////////////////////////////////////////////////////////////////////
// C1 CPP file
void CALLBACK C1::F1(int i)
{
// Do stuff with C1, its methods and data, and even do stuff with the passed in parameter
}
/////////////////////////////////////////////////////////////////////
// C2 H File
class C1; // Forward declaration
class C2
{
typedef void (CALLBACK C1::* pfnCallBack)(int i);
public:
C2() {};
~C2() {};
void Fn(C1 * pThat,pfnCallBack pFn);
};
/////////////////////////////////////////////////////////////////////
// C2 CPP File
void C2::Fn(C1 * pThat,pfnCallBack pFn)
{
// Call a non-static method in C1
int i = 1;
(pThat->*pFn)(i);
}
У піднімати торг signals2 дозволяє підписатися загальні функції - члени (без шаблонів!) І в поточно - чином.
Приклад: Сигнали перегляду документа можуть бути використані для реалізації гнучких архітектур перегляду документів. Документ буде містити сигнал, до якого може підключитися кожен із поглядів. Наступний клас Document визначає простий текстовий документ, який підтримує перегляд кількох зображень. Зауважте, що він зберігає єдиний сигнал, до якого будуть підключені всі види.
class Document
{
public:
typedef boost::signals2::signal<void ()> signal_t;
public:
Document()
{}
/* Connect a slot to the signal which will be emitted whenever
text is appended to the document. */
boost::signals2::connection connect(const signal_t::slot_type &subscriber)
{
return m_sig.connect(subscriber);
}
void append(const char* s)
{
m_text += s;
m_sig();
}
const std::string& getText() const
{
return m_text;
}
private:
signal_t m_sig;
std::string m_text;
};
Далі ми можемо почати визначати погляди. Наступний клас TextView забезпечує простий перегляд тексту документа.
class TextView
{
public:
TextView(Document& doc): m_document(doc)
{
m_connection = m_document.connect(boost::bind(&TextView::refresh, this));
}
~TextView()
{
m_connection.disconnect();
}
void refresh() const
{
std::cout << "TextView: " << m_document.getText() << std::endl;
}
private:
Document& m_document;
boost::signals2::connection m_connection;
};
Прийнята відповідь є вичерпною, але пов'язаною з питанням, я просто хочу навести простий приклад тут. У мене був код, який я його написав давно. Мені хотілося обрізати дерево порядковим способом (лівий вузол, потім кореневий вузол, потім правий вузол), і коли б я дійшов до одного Вузла, я хотів би мати можливість викликати довільну функцію, щоб це могло робити все.
void inorder_traversal(Node *p, void *out, void (*callback)(Node *in, void *out))
{
if (p == NULL)
return;
inorder_traversal(p->left, out, callback);
callback(p, out); // call callback function like this.
inorder_traversal(p->right, out, callback);
}
// Function like bellow can be used in callback of inorder_traversal.
void foo(Node *t, void *out = NULL)
{
// You can just leave the out variable and working with specific node of tree. like bellow.
// cout << t->item;
// Or
// You can assign value to out variable like below
// Mention that the type of out is void * so that you must firstly cast it to your proper out.
*((int *)out) += 1;
}
// This function use inorder_travesal function to count the number of nodes existing in the tree.
void number_nodes(Node *t)
{
int sum = 0;
inorder_traversal(t, &sum, foo);
cout << sum;
}
int main()
{
Node *root = NULL;
// What These functions perform is inserting an integer into a Tree data-structure.
root = insert_tree(root, 6);
root = insert_tree(root, 3);
root = insert_tree(root, 8);
root = insert_tree(root, 7);
root = insert_tree(root, 9);
root = insert_tree(root, 10);
number_nodes(root);
}