C++中的回调函数
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2298242/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Callback functions in C++
提问by cpx
In C++, when and how do you use a callback function?
在 C++ 中,何时以及如何使用回调函数?
EDIT:
I would like to see a simple example to write a callback function.
编辑:
我想看一个简单的例子来编写一个回调函数。
回答by Pixelchemist
Note: Most of the answers cover function pointers which is one possibility to achieve "callback" logic in C++, but as of today not the most favourable one I think.
注意:大多数答案都涵盖了函数指针,这是在 C++ 中实现“回调”逻辑的一种可能性,但到目前为止,我认为这不是最有利的。
What are callbacks(?) and why to use them(!)
什么是回调(?)以及为什么要使用它们(!)
A callback is a callable(see further down) accepted by a class or function, used to customize the current logic depending on that callback.
回调是一个类或函数接受的可调用(见下文),用于根据该回调自定义当前逻辑。
One reason to use callbacks is to write genericcode which is independant from the logic in the called function and can be reused with different callbacks.
使用回调的原因之一是编写通用代码,该代码独立于被调用函数中的逻辑,并且可以与不同的回调一起使用。
Many functions of the standard algorithms library <algorithm>
use callbacks. For example the for_each
algorithm applies an unary callback to every item in a range of iterators:
标准算法库的许多函数都<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;
}
which can be used to first increment and then print a vector by passing appropriate callables for example:
它可用于首先递增,然后通过传递适当的可调用对象来打印向量,例如:
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 << " "; });
which prints
哪个打印
5 6.2 8 9.5 11.2
Another application of callbacks is the notification of callers of certain events which enables a certain amount of static / compile time flexibility.
回调的另一个应用是通知某些事件的调用者,这实现了一定程度的静态/编译时间灵活性。
Personally, I use a local optimization library that uses two different callbacks:
就我个人而言,我使用了一个使用两种不同回调的本地优化库:
- The first callback is called if a function value and the gradient based on a vector of input values is required (logic callback: function value determination / gradient derivation).
- The second callback is called once for each algorithm step and receives certain information about the convergence of the algorithm (notification callback).
- 如果需要函数值和基于输入值向量的梯度(逻辑回调:函数值确定/梯度推导),则调用第一个回调。
- 第二个回调为每个算法步骤调用一次,并接收有关算法收敛的某些信息(通知回调)。
Thus, the library designer is not in charge of deciding what happens with the information that is given to the programmer via the notification callback and he needn't worry about how to actually determine function values because they're provided by the logic callback. Getting those things right is a task due to the library user and keeps the library slim and more generic.
因此,库设计者不负责决定通过通知回调提供给程序员的信息会发生什么,他不必担心如何实际确定函数值,因为它们是由逻辑回调提供的。把这些事情做好是图书馆用户的一项任务,让图书馆保持苗条和更通用。
Furthermore, callbacks can enable dynamic runtime behaviour.
此外,回调可以启用动态运行时行为。
Imagine some kind of game engine class which has a function that is fired, each time the users presses a button on his keyboard and a set of functions that control your game behaviour. With callbacks you can (re)decide at runtime which action will be taken.
想象一下某种游戏引擎类,它有一个函数被触发,每次用户按下键盘上的一个按钮和一组控制你的游戏行为的函数。通过回调,您可以(重新)决定在运行时将采取哪些操作。
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;
}
};
Here the function key_pressed
uses the callbacks stored in actions
to obtain the desired behaviour when a certain key is pressed.
If the player chooses to change the button for jumping, the engine can call
当按下某个键时,该函数key_pressed
使用存储在其中的回调actions
来获取所需的行为。如果玩家选择改变跳跃按钮,引擎可以调用
game_core_instance.update_keybind(newly_selected_key, &player_jump);
and thus change the behaviour of a call to key_pressed
(which the calls player_jump
) once this button is pressed the next time ingame.
从而在下次游戏中按下此按钮后更改调用key_pressed
(调用player_jump
)的行为。
What are callablesin C++(11)?
C++(11)中的可调用对象是什么?
See C++ concepts: Callableon cppreference for a more formal description.
有关更正式的描述,请参阅C++ 概念:可在 cppreference 上调用。
Callback functionality can be realized in several ways in C++(11) since several different things turn out to be callable*:
回调功能可以在 C++(11) 中以多种方式实现,因为有几种不同的东西是可调用的*:
- Function pointers (including pointers to member functions)
std::function
objects- Lambda expressions
- Bind expressions
- Function objects (classes with overloaded function call operator
operator()
)
- 函数指针(包括指向成员函数的指针)
std::function
对象- Lambda 表达式
- 绑定表达式
- 函数对象(具有重载函数调用运算符的类
operator()
)
* Note: Pointer to data members are callable as well but no function is called at all.
*注意:指向数据成员的指针也是可调用的,但根本不调用任何函数。
Several important ways to write callbacksin detail
详细编写回调的几种重要方式
- X.1 "Writing" a callback in this post means the syntax to declare and name the callback type.
- X.2 "Calling" a callback refers to the syntax to call those objects.
- X.3 "Using" a callback means the syntax when passing arguments to a function using a callback.
- X.1 在这篇文章中“编写”一个回调是指声明和命名回调类型的语法。
- X.2 “调用”回调是指调用这些对象的语法。
- X.3 “使用”回调是指使用回调将参数传递给函数时的语法。
Note: As of C++17, a call like f(...)
can be written as std::invoke(f, ...)
which also handles the pointer to member case.
注意:从 C++17 开始,像这样的调用f(...)
可以写成std::invoke(f, ...)
同样处理指向成员大小写的指针。
1. Function pointers
1. 函数指针
A function pointer is the 'simplest' (in terms of generality; in terms of readability arguably the worst) type a callback can have.
函数指针是回调可以具有的“最简单”(就通用性而言;就可读性而言可能是最差的)类型。
Let's have a simple function foo
:
让我们有一个简单的功能foo
:
int foo (int x) { return 2+x; }
1.1 Writing a function pointer / type notation
1.1 编写函数指针/类型符号
A function pointer typehas the notation
甲函数指针类型具有符号
return_type (*)(parameter_type_1, parameter_type_2, parameter_type_3)
// i.e. a pointer to foo has the type:
int (*)(int)
where a named function pointertype will look like
其中一个名为函数指针类型的样子
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;
The using
declaration gives us the option to make things a little bit more readable, since the typedef
for f_int_t
can also be written as:
该using
声明给我们的选项,以使事情更易读一点点,因为typedef
对f_int_t
也可以写为:
using f_int_t = int(*)(int);
Where (at least for me) it is clearer that f_int_t
is the new type alias and recognition of the function pointer type is also easier
其中(至少对我而言)更清楚的f_int_t
是新类型别名和函数指针类型的识别也更容易
And a declaration of a function using a callback of function pointer typewill be:
使用函数指针类型的回调函数声明将是:
// 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);
1.2 Callback call notation
1.2 回调调用符号
The call notation follows the simple function call syntax:
调用符号遵循简单的函数调用语法:
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
}
1.3 Callback use notation and compatible types
1.3 回调使用符号和兼容类型
A callback function taking a function pointer can be called using function pointers.
可以使用函数指针调用带有函数指针的回调函数。
Using a function that takes a function pointer callback is rather simple:
使用接受函数指针回调的函数相当简单:
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
1.4 Example
1.4 示例
A function ca be written that doesn't rely on how the callback works:
可以编写一个不依赖于回调如何工作的函数:
void tranform_every_int(int * v, unsigned n, int (*fp)(int))
{
for (unsigned i = 0; i < n; ++i)
{
v[i] = fp(v[i]);
}
}
where possible callbacks could be
可能的回调可能在哪里
int double_int(int x) { return 2*x; }
int square_int(int x) { return x*x; }
used like
用过
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};
2. Pointer to member function
2. 成员函数指针
A pointer to member function (of some class C
) is a special type of (and even more complex) function pointer which requires an object of type C
to operate on.
指向成员函数(某个类C
)的指针是一种特殊类型(甚至更复杂)的函数指针,它需要一个类型的对象C
来操作。
struct C
{
int y;
int foo(int x) const { return x+y; }
};
2.1 Writing pointer to member function / type notation
2.1 编写指向成员函数/类型符号的指针
A pointer to member function typefor some class T
has the notation
甲成员函数指针类型对于某些类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)
where a named pointer to member functionwill -in analogy to the function pointer- look like this:
其中指向成员函数的命名指针将 - 类似于函数指针 - 看起来像这样:
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;
Example: Declaring a function taking a pointer to member function callbackas one of its arguments:
示例:声明一个函数,将指向成员函数回调的指针作为其参数之一:
// 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);
2.2 Callback call notation
2.2 回调调用符号
The pointer to member function of C
can be invoked, with respect to an object of type C
by using member access operations on the dereferenced pointer.
Note: Parenthesis required!
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
}
Note: If a pointer to C
is available the syntax is equivalent (where the pointer to C
must be dereferenced as well):
注意:如果指向的指针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);
}
2.3 Callback use notation and compatible types
2.3 回调使用符号和兼容类型
A callback function taking a member function pointer of class T
can be called using a member function pointer of class T
.
T
可以使用class的成员函数指针调用采用class 的成员函数指针的回调函数T
。
Using a function that takes a pointer to member function callback is -in analogy to function pointers- quite simple as well:
使用带有指向成员函数回调指针的函数 - 类似于函数指针 - 也非常简单:
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
3. std::function
objects (header <functional>
)
3.std::function
对象(标题<functional>
)
The std::function
class is a polymorphic function wrapper to store, copy or invoke callables.
该std::function
班是一个多态函数包装到存储,复制或调用可调用。
3.1 Writing a std::function
object / type notation
3.1 编写std::function
对象/类型符号
The type of a std::function
object storing a callable looks like:
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;
3.2 Callback call notation
3.2 回调调用符号
The class std::function
has operator()
defined which can be used to invoke its target.
该类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
}
3.3 Callback use notation and compatible types
3.3 回调使用符号和兼容类型
The std::function
callback is more generic than function pointers or pointer to member function since different types can be passed and implicitly converted into a std::function
object.
该std::function
回调比函数指针或指针成员函数更为通用的,因为不同类型的可以被传递并隐式转换成std::function
对象。
3.3.1 Function pointers and pointers to member functions
3.3.1 函数指针和成员函数指针
A function pointer
一个函数指针
int a = 2;
int b = stdf_foobar(a, &foo);
// b == 6 ( 2 + (2+2) )
or a pointer to member function
或指向成员函数的指针
int a = 2;
C my_c{7}; // aggregate initialization
int b = stdf_C_foobar(a, c, &C::foo);
// b == 11 == ( 2 + (7+2) )
can be used.
可以使用。
3.3.2 Lambda expressions
3.3.2 Lambda 表达式
An unnamed closure from a lambda expression can be stored in a std::function
object:
来自 lambda 表达式的未命名闭包可以存储在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
expressions
3.3.3std::bind
表达式
The result of a std::bind
expression can be passed. For example by binding parameters to a function pointer call:
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 )
Where also objects can be bound as the object for the invocation of pointer to member functions:
也可以将对象绑定为调用成员函数指针的对象:
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 Function objects
3.3.4 函数对象
Objects of classes having a proper operator()
overload can be stored inside a std::function
object, as well.
具有适当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 )
3.4 Example
3.4 示例
Changing the function pointer example to use std::function
更改要使用的函数指针示例 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]);
}
}
gives a whole lot more utility to that function because (see 3.3) we have more possibilities to use it:
为该函数提供了更多的实用性,因为(参见 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};
4. Templated callback type
4. 模板化回调类型
Using templates, the code calling the callback can be even more general than using std::function
objects.
使用模板,调用回调的代码甚至可以比使用std::function
对象更通用。
Note that templates are a compile-time feature and are a design tool for compile-time polymorphism. If runtime dynamic behaviour is to be achieved through callbacks, templates will help but they won't induce runtime dynamics.
请注意,模板是编译时功能,是编译时多态性的设计工具。如果要通过回调实现运行时动态行为,模板会有所帮助,但它们不会引起运行时动态。
4.1 Writing (type notations) and calling templated callbacks
4.1 编写(类型符号)和调用模板化回调
Generalizing i.e. the std_ftransform_every_int
code from above even further can be achieved by using templates:
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]);
}
}
with an even more general (as well as easiest) syntax for a callback type being a plain, to-be-deduced templated argument:
回调类型的语法更通用(也是最简单的),它是一个简单的、待推导的模板化参数:
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]);
}
}
Note: The included output prints the type name deduced for templated type F
. The implementation of type_name
is given at the end of this post.
注意:包含的输出打印为模板化类型推导出的类型名称F
。type_name
本文末尾给出了 的实现。
The most general implementation for the unary transformation of a range is part of the standard library, namely std::transform
,
which is also templated with respect to the iterated types.
范围的一元转换的最通用实现是标准库的一部分,即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;
}
4.2 Examples using templated callbacks and compatible types
4.2 使用模板化回调和兼容类型的示例
The compatible types for the templated std::function
callback method stdf_transform_every_int_templ
are identical to the above mentioned types (see 3.4).
模板化std::function
回调方法的兼容类型stdf_transform_every_int_templ
与上述类型相同(参见 3.4)。
Using the templated version however, the signature of the used callback may change a little:
然而,使用模板化版本,所使用的回调的签名可能会发生一些变化:
// 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);
Note: std_ftransform_every_int
(non templated version; see above) does work with foo
but not using muh
.
注意:(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";
}
The plain templated parameter of transform_every_int_templ
can be every possible callable type.
的普通模板参数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);
The above code prints:
上面的代码打印:
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
implementation used above
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;
}
回答by Ramon Zarazua B.
There is also the C way of doing callbacks: function pointers
还有做回调的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);
}
Now if you want to pass in class methods as callbacks, the declarations to those function pointers have more complex declarations, example:
现在,如果您想将类方法作为回调传递,则这些函数指针的声明具有更复杂的声明,例如:
//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);
}
回答by Karl von Moor
Scott Meyers gives a nice example:
Scott Meyers 举了一个很好的例子:
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;
};
I think the example says it all.
我认为这个例子说明了一切。
std::function<>
is the "modern" way of writing C++ callbacks.
std::function<>
是编写 C++ 回调的“现代”方式。
回答by Reed Copsey
A Callback functionis a method that is passed into a routine, and called at some point by the routine to which it is passed.
甲回调函数是传递到一个程序中,并呼吁在由其所传递的例程的某一点的方法。
This is very useful for making reusable software. For example, many operating system APIs (such as the Windows API) use callbacks heavily.
这对于制作可重用的软件非常有用。例如,许多操作系统 API(如 Windows API)大量使用回调。
For example, if you wanted to work with files in a folder - you can call an API function, with your own routine, and your routine gets run once per file in the specified folder. This allows the API to be very flexible.
例如,如果您想处理文件夹中的文件 - 您可以使用您自己的例程调用 API 函数,并且您的例程会针对指定文件夹中的每个文件运行一次。这使得 API 非常灵活。
回答by Miljen Mikic
The accepted answer is very useful and quite comprehensive. However, the OP states
接受的答案非常有用且非常全面。然而,OP指出
I would like to see a simple exampleto write a callback function.
我想看一个简单的例子来编写一个回调函数。
So here you go, from C++11 you have std::function
so there is no need for function pointers and similar stuff:
所以你开始了,从 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;
}
This example is by the way somehow real, because you wish to call function print_hashes
with different implementations of hash functions, for this purpose I provided a simple one. It receives a string, returns an int (a hash value of the provided string), and all that you need to remember from the syntax part is std::function<int (const std::string&)>
which describes such function as an input argument of the function that will invoke it.
顺便说一下,这个例子有点真实,因为你希望print_hashes
用不同的哈希函数实现来调用函数,为此我提供了一个简单的。它接收一个字符串,返回一个 int(所提供字符串的散列值),您需要从语法部分记住的就是std::function<int (const std::string&)>
将此类函数描述为将调用它的函数的输入参数。
回答by Darryl
There isn't an explicit concept of a callback function in C++. Callback mechanisms are often implemented via function pointers, functor objects, or callback objects. The programmers have to explicitly design and implement callback functionality.
C++ 中没有明确的回调函数概念。回调机制通常通过函数指针、函子对象或回调对象来实现。程序员必须明确设计和实现回调功能。
Edit based on feedback:
根据反馈编辑:
In spite of the negative feedback this answer has received, it is not wrong. I'll try to do a better job of explaining where I'm coming from.
尽管这个答案收到了负面反馈,但这并没有错。我会尽量更好地解释我来自哪里。
C and C++ have everything you need to implement callback functions. The most common and trivial way to implement a callback function is to pass a function pointer as a function argument.
C 和 C++ 拥有实现回调函数所需的一切。实现回调函数最常见和最简单的方法是将函数指针作为函数参数传递。
However, callback functions and function pointers are not synonymous. A function pointer is a language mechanism, while a callback function is a semantic concept. Function pointers are not the only way to implement a callback function - you can also use functors and even garden variety virtual functions. What makes a function call a callback is not the mechanism used to identify and call the function, but the context and semantics of the call. Saying something is a callback function implies a greater than normal separation between the calling function and the specific function being called, a looser conceptual coupling between the caller and the callee, with the caller having explicit control over what gets called. It is that fuzzy notion of looser conceptual coupling and caller-driven function selection that makes something a callback function, not the use of a function pointer.
但是,回调函数和函数指针不是同义词。函数指针是一种语言机制,而回调函数是一种语义概念。函数指针不是实现回调函数的唯一方法——您还可以使用函子甚至各种虚拟函数。使函数调用成为回调的不是用于识别和调用函数的机制,而是调用的上下文和语义。说某事是回调函数意味着调用函数和被调用的特定函数之间比正常分离更大,调用者和被调用者之间的概念耦合更松散,调用者可以明确控制被调用的内容。
For example, the .NET documentation for IFormatProvidersays that "GetFormat is a callback method", even though it is just a run-of-the-mill interface method. I don't think anyone would argue that all virtual method calls are callback functions. What makes GetFormat a callback method is not the mechanics of how it is passed or invoked, but the semantics of the caller picking which object's GetFormat method will be called.
例如,IFormatProvider的 .NET 文档说“GetFormat 是一个回调方法”,即使它只是一个普通的接口方法。我认为没有人会争辩说所有虚方法调用都是回调函数。使 GetFormat 成为回调方法的原因不是它如何传递或调用的机制,而是调用者选择将调用哪个对象的 GetFormat 方法的语义。
Some languages include features with explicit callback semantics, typically related to events and event handling. For example, C# has the eventtype with syntax and semantics explicitly designed around the concept of callbacks. Visual Basic has its Handlesclause, which explicitly declares a method to be a callback function while abstracting away the concept of delegates or function pointers. In these cases, the semantic concept of a callback is integrated into the language itself.
某些语言包括具有显式回调语义的功能,通常与事件和事件处理相关。例如,C# 具有围绕回调概念明确设计的语法和语义的事件类型。Visual Basic 有它的Handles子句,它显式地将方法声明为回调函数,同时抽象出委托或函数指针的概念。在这些情况下,回调的语义概念被集成到语言本身中。
C and C++, on the other hand, does not embed the semantic conceptof callback functions nearly as explicitly. The mechanisms are there, the integrated semantics are not. You can implement callback functions just fine, but to get something more sophisticated which includes explicit callback semantics you have to build it on top of what C++ provides, such as what Qt did with their Signals and Slots.
另一方面,C 和 C++几乎没有明确地嵌入回调函数的语义概念。机制存在,集成语义不存在。您可以很好地实现回调函数,但要获得更复杂的东西,包括显式回调语义,您必须在 C++ 提供的内容之上构建它,例如 Qt 对其Signals 和 Slots所做的。
In a nutshell, C++ has what you need to implement callbacks, often quite easily and trivially using function pointers. What it does not have is keywords and features whose semantics are specific to callbacks, such as raise, emit, Handles, event +=, etc. If you're coming from a language with those types of elements, the native callback support in C++ will feel neutered.
简而言之,C++ 具有实现回调所需的一切,通常使用函数指针非常容易和微不足道。它没有的是语义特定于回调的关键字和功能,例如raise、emit、Handles、event +=等。 如果您来自具有这些类型元素的语言,则 C++ 中的本机回调支持会感到绝育。
回答by AudioDroid
Callback functions are part of the C standard, an therefore also part of C++. But if you are working with C++, I would suggest you use the observer patterninstead: http://en.wikipedia.org/wiki/Observer_pattern
回调函数是 C 标准的一部分,因此也是 C++ 的一部分。但是,如果您使用 C++,我建议您改用观察者模式:http: //en.wikipedia.org/wiki/Observer_pattern
回答by Gravy Jones
See the above definition where it states that a callback function is passed off to some other function and at some point it is called.
参见上面的定义,它指出回调函数被传递给其他函数,并在某个时候被调用。
In C++ it is desirable to have callback functions call a classes method. When you do this you have access to the member data. If you use the C way of defining a callback you will have to point it to a static member function. This is not very desirable.
在 C++ 中,希望回调函数调用类方法。当您这样做时,您可以访问成员数据。如果使用 C 方式定义回调,则必须将其指向静态成员函数。这不是很理想。
Here is how you can use callbacks in C++. Assume 4 files. A pair of .CPP/.H files for each class. Class C1 is the class with a method we want to callback. C2 calls back to C1's method. In this example the callback function takes 1 parameter which I added for the readers sake. The example doesn't show any objects being instantiated and used. One use case for this implementation is when you have one class that reads and stores data into temporary space and another that post processes the data. With a callback function, for every row of data read the callback can then process it. This technique cuts outs the overhead of the temporary space required. It is particularly useful for SQL queries that return a large amount of data which then has to be post-processed.
以下是在 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);
}
回答by crizCraig
Boost's signals2allows you to subscribe generic member functions (without templates!) and in a threadsafe way.
Boost 的signals2允许您以线程安全的方式订阅通用成员函数(无需模板!)。
Example: Document-View Signals can be used to implement flexible Document-View architectures. The document will contain a signal to which each of the views can connect. The following Document class defines a simple text document that supports mulitple views. Note that it stores a single signal to which all of the views will be connected.
示例:文档视图信号可用于实现灵活的文档视图架构。该文档将包含一个信号,每个视图都可以连接到该信号。以下 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;
};
Next, we can begin to define views. The following TextView class provides a simple view of the document 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;
};
回答by Ehsan Ahmadi
The accepted answer is comprehensive but related to the question i just want to put an simple example here. I had a code that i'd written it a long time ago. i wanted to traverse a tree with in-order way (left-node then root-node then right-node) and whenever i reach to one Node i wanted to be able to call a arbitrary function so that it could do everything.
接受的答案是全面的,但与问题有关,我只想在这里举一个简单的例子。我有一个我很久以前写的代码。我想以有序的方式遍历一棵树(左节点,然后是根节点,然后是右节点),每当我到达一个节点时,我都希望能够调用任意函数,以便它可以做任何事情。
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);
}