C++中的回调函数
Posted
技术标签:
【中文标题】C++中的回调函数【英文标题】:Callback functions in C++ 【发布时间】:2011-01-18 21:17:22 【问题描述】:在 C++ 中,何时以及如何使用回调函数?
编辑: 我想看一个写回调函数的简单例子。
【问题讨论】:
[This] (thispointer.com/…) 很好地解释了回调函数的基础知识,并且易于理解。 【参考方案1】:注意:大多数答案都包含函数指针,这是在 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++(11) 中的callables?
有关更正式的描述,请参阅 cppreference 上的 C++ concepts: Callable。
回调功能可以在 C++(11) 中以多种方式实现,因为有几种不同的东西被证明是可调用*:
函数指针(包括指向成员函数的指针)std::function
对象
Lambda 表达式
绑定表达式
函数对象(具有重载函数调用运算符operator()
的类)
* 注意:指向数据成员的指针也是可调用的,但根本不调用任何函数。
回调的几个重要写法详解
X.1 在这篇文章中“编写”回调意味着声明和命名回调类型的语法。 X.2“调用”回调是指调用这些对象的语法。 X.3“使用”回调是指使用回调将参数传递给函数时的语法。注意:从 C++17 开始,像 f(...)
这样的调用可以写成 std::invoke(f, ...)
,它还处理指向成员大小写的指针。
1。函数指针
函数指针是回调可以具有的“最简单”(就通用性而言;就可读性而言可能是最差的)类型。
我们来个简单的函数foo
:
int foo (int x) return 2+x;
1.1 编写函数指针/类型表示法
函数指针类型有符号
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
声明让我们可以选择让内容更具可读性,因为f_int_t
的typedef
也可以写成:
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);
1.2 回调调用符号
调用符号遵循简单的函数调用语法:
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 回调使用表示法和兼容类型
可以使用函数指针调用带有函数指针的回调函数。
使用带有函数指针回调的函数相当简单:
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 示例
可以编写一个不依赖于回调如何工作的函数:
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;
2。指向成员函数的指针
指向成员函数的指针(属于某个类C
)是一种特殊类型的(甚至更复杂的)函数指针,需要C
类型的对象才能对其进行操作。
struct C
int y;
int foo(int x) const return x+y;
;
2.1 编写指向成员函数/类型符号的指针
某个类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);
2.2 回调调用符号
对于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);
2.3 回调使用表示法和兼容类型
可以使用类T
的成员函数指针调用获取类T
的成员函数指针的回调函数。
使用带有指向成员函数回调的指针的函数——类似于函数指针——也非常简单:
C my_c2; // 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
对象(标题<functional>
)
std::function
类是一个多态函数包装器,用于存储、复制或调用可调用对象。
3.1 编写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;
3.2 回调调用符号
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 回调使用表示法和兼容类型
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_c7; // aggregate initialization
int b = stdf_C_foobar(a, c, &C::foo);
// b == 11 == ( 2 + (7+2) )
可以使用。
3.3.2 Lambda 表达式
来自 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
表达式
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_c7; // 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, Meow8);
// b == 99 == 11 + ( 8 * 11 )
3.4 示例
将函数指针示例更改为使用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;
4。模板化回调类型
使用模板,调用回调的代码可以比使用std::function
对象更通用。
请注意,模板是编译时特性,是编译时多态性的设计工具。如果要通过回调实现运行时动态行为,模板会有所帮助,但不会引发运行时动态。
4.1 编写(类型符号)和调用模板化回调
可以通过使用模板进一步概括,即上面的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;
4.2 使用模板化回调和兼容类型的示例
模板化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;
【讨论】:
@BogeyJammer:如果您没有注意到:答案有两个部分。 1. 用一个小例子对“回调”的一般解释。 2. 不同可调用对象的完整列表以及使用回调编写代码的方法。欢迎您不要深入研究或阅读整个答案,但仅仅因为您不想要详细的视图,答案并非无效或“野蛮复制”。主题是“c++ 回调”。即使第 1 部分适用于 OP,其他人可能会发现第 2 部分有用。随意指出第一部分缺乏信息或建设性批评,而不是-1。 第 1 部分对初学者不够友好且不够清晰。我不能说它没有让我学到一些东西来更具建设性。并且第 2 部分没有被请求,淹没了页面并且是不可能的,即使你假装它很有用,尽管事实上它通常在专门的文档中找到,这些详细信息首先被查找。我绝对保留反对票。一票代表个人意见,请接受并尊重。 @BogeyJammer 我对编程并不陌生,但我对“现代 c++”很陌生。这个答案给了我确切的上下文,我需要推理回调在 C++ 中所扮演的角色。 OP 可能没有要求提供多个示例,但在 SO 上的习惯是,在永无止境的追求教育一个愚蠢的世界,列举一个问题的所有可能的解决方案。如果它读起来太像一本书,我能提供的唯一建议是通过阅读a few of them 来练习一下。int b = foobar(a, foo); // call foobar with pointer to foo as callback
,这是错字吧? foo
应该是这个工作 AFAIK 的指针。
@konoufo: [conv.func]
的 C++11 标准说:“函数类型 T 的左值可以转换为“指向 T 的指针”类型的右值。结果是指向函数的指针。" 这是标准转换,因此隐式发生。 (当然)可以在这里使用函数指针。【参考方案2】:
还有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);
【讨论】:
类方法示例有错误。调用应该是:(instance.*callback)(1.0f) 这与 std::tr1:function 的缺点是回调是按类键入的;当执行调用的对象不知道要调用的对象的类时,这使得使用 C 风格的回调变得不切实际。 是的,你可以。typedef
只是语法糖,使其更具可读性。如果没有typedef
,函数指针的DoWorkObject 定义将是:void DoWorkObject(int (*callback)(float))
。对于成员指针将是:void DoWorkObject(int (ClassName::*callback)(float))
谢谢!简单易懂!不像其他人那样弯曲。
@Milan 我刚刚投票否决了您最近提议的编辑,其摘要是“以前的编辑刚刚删除了有用的评论(甚至不关心写适当的摘要。他/她只是复制-粘贴了摘要!!)”。解释发生了什么:我敢打赌,您尝试撤消的编辑(@Tarmo)来自提议编辑的审核过程;审阅者有机会“进一步编辑”您的提案,这实际上显示为具有相同摘要的单独编辑(不幸的是)。【参考方案3】:
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;
;
我认为这个例子说明了一切。
std::function<>
是编写 C++ 回调的“现代”方式。
【讨论】:
出于兴趣,SM在哪本书中给出了这个例子?干杯:) 我知道这是旧的,但是因为我几乎开始这样做并且它最终无法在我的设置 (mingw) 上运行,如果您使用的是 GCC 版本 = 4.0.1 中进行大量工作,我使用的一些依赖项将无法编译,所以我坚持使用良好的老式 C 风格回调,它工作得很好。【参考方案4】:Callback function 是一种方法,它被传递到例程中,并在某个时刻被它传递到的例程调用。
这对于制作可重用的软件非常有用。例如,许多操作系统 API(如 Windows API)大量使用回调。
例如,如果您想处理文件夹中的文件 - 您可以使用自己的例程调用 API 函数,并且您的例程会针对指定文件夹中的每个文件运行一次。这使得 API 非常灵活。
【讨论】:
这个答案真的不会让普通程序员说出他不知道的任何事情。我在熟悉许多其他语言的同时学习 C++。我一般不关心什么回调。 问题是如何使用回调,而不是如何定义它们。【参考方案5】:接受的答案非常有用且非常全面。但是,OP声明
我想看一个简单的例子来写一个回调函数。
所以你去吧,从 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&)>
,它将此类函数描述为将调用它的函数的输入参数。
【讨论】:
在上述所有答案中,这个答案让我了解了回调是什么以及如何使用它们。谢谢。 @MeharCharanSahai 很高兴听到这个消息:)不客气。 这让我终于明白了,谢谢。我认为有时工程师应该不那么认真地对待它们,并理解最终的技能是有意识地简化不简单的事情,IMO。【参考方案6】:C++ 中没有明确的回调函数概念。回调机制通常通过函数指针、仿函数对象或回调对象来实现。程序员必须明确地设计和实现回调功能。
根据反馈进行编辑:
尽管这个答案收到了负面反馈,但它并没有错。我会努力更好地解释我来自哪里。
C 和 C++ 拥有实现回调函数所需的一切。实现回调函数最常见和最简单的方法是将函数指针作为函数参数传递。
但是,回调函数和函数指针不是同义词。函数指针是一种语言机制,而回调函数是一个语义概念。函数指针不是实现回调函数的唯一方法——您还可以使用仿函数,甚至可以使用各种虚拟函数。使函数调用回调的不是用于识别和调用函数的机制,而是调用的上下文和语义。说某事是回调函数意味着调用函数和被调用的特定函数之间的分离比正常情况更大,调用者和被调用者之间的概念耦合更松散,调用者可以明确控制被调用的内容。正是这种更松散的概念耦合和调用者驱动的函数选择的模糊概念使某些东西成为回调函数,而不是使用函数指针。
例如,IFormatProvider 的 .NET 文档说 “GetFormat 是一个回调方法”,尽管它只是一个普通的接口方法。我认为没有人会争辩说所有虚拟方法调用都是回调函数。使 GetFormat 成为回调方法的原因并不在于其传递或调用方式的机制,而是调用者选择将调用哪个对象的 GetFormat 方法的语义。
某些语言包含具有显式回调语义的功能,通常与事件和事件处理相关。例如,C# 具有 event 类型,其语法和语义明确地围绕回调的概念设计。 Visual Basic 有它的 Handles 子句,它显式声明一个方法为回调函数,同时抽象出委托或函数指针的概念。在这些情况下,回调的语义概念被集成到语言本身中。
另一方面,C 和 C++ 并没有几乎明确地嵌入回调函数的语义概念。机制在那里,集成语义不存在。你可以很好地实现回调函数,但要获得更复杂的东西,包括显式回调语义,你必须在 C++ 提供的基础上构建它,例如 Qt 对他们的Signals and Slots 所做的。
简而言之,C++ 具有实现回调所需的功能,通常使用函数指针非常容易且微不足道。它没有的是语义特定于回调的关键字和特性,例如 raise、emit、Handles、event + = 等。如果您来自具有这些类型元素的语言,C++ 中的本机回调支持将感觉中性。
【讨论】:
幸好这不是我访问此页面时看到的第一个答案,否则我会立即反弹!【参考方案7】:回调函数是 C 标准的一部分,因此也是 C++ 的一部分。但如果您使用 C++,我建议您改用 观察者模式:http://en.wikipedia.org/wiki/Observer_pattern
【讨论】:
回调函数不一定与通过作为参数传递的函数指针执行函数同义。根据某些定义,回调函数一词带有额外的语义,即通知其他代码刚刚发生的事情,或者是应该发生的事情的时间。从这个角度来看,回调函数不是 C 标准的一部分,但可以使用标准的函数指针轻松实现。 “C 标准的一部分,因此也是 C++ 的一部分。”这是一个典型的误解,但仍然是一个误解:-) 我必须同意。我将保持原样,因为如果我现在更改它只会引起更多混乱。我的意思是说函数指针(!)是标准的一部分。说任何与此不同的东西——我同意——都是误导性的。 回调函数以何种方式成为“C 标准的一部分”?我不认为它支持函数和指向函数的指针这一事实意味着它专门将回调规范为一种语言概念。此外,如前所述,即使它是准确的,它也不会与 C++ 直接相关。当 OP 询问“何时以及如何”在 C++ 中使用回调(这是一个蹩脚的、过于广泛的问题,但仍然如此)时,这尤其不相关,而您的答案是一个仅链接的警告,建议您做一些不同的事情。【参考方案8】:参见上面的定义,它声明一个回调函数被传递给其他函数并在某个时候被调用。
在 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);
【讨论】:
【参考方案9】:接受的答案很全面,但与我只想在这里举一个简单例子的问题有关。我有一个很久以前写的代码。我想以有序的方式遍历一棵树(左节点,然后是根节点,然后是右节点),每当我到达一个节点时,我希望能够调用任意函数,以便它可以做任何事情。
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);
【讨论】:
它是如何回答问题的? 你知道接受的答案是正确和全面的,我认为一般没有什么可说的。但我发布了一个我使用回调函数的示例。【参考方案10】:Boost 的signals2 允许您以线程安全的方式订阅通用成员函数(无需模板!)。
示例:文档视图信号可用于实现灵活的 文档视图架构。该文件将包含一个信号 每个视图都可以连接。以下文档类 定义一个支持多视图的简单文本文档。注意 它存储一个信号,所有视图都将连接到该信号。
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;
;
【讨论】:
【参考方案11】:@Pixelchemist 已经给出了全面的答案。但作为一名网络开发人员,我可以提供一些建议。
通常我们使用tcp
开发a web framework
,所以通常我们有一个结构:
TcpServer listen port and register the socket to epoll or something
-> TcpServer receive new connection
-> HttpConenction deal the data from the connection
-> HttpServer call Handler to deal with HttpConnection.
-> Handler contain codes like save into database and fetch from db
我们可以按顺序开发框架,但是对只想关心Handler
的用户不友好。所以是时候使用callback
了。
Mutiple Handler written by user
-> register the handler as callback property of HttpServer
-> register the related methods in HttpServer to HttpConnection
-> register the relate methods in HttpConnection to TcpServer
所以用户只需要注册他们的处理程序到httpserver(usually with some path string as key
),其他的事情是通用框架可以做的。
所以你会发现我们可以将callback
视为一种上下文,我们希望委托给其他人为我们做。核心是we don't know when is the best time to invoke the function, but the guy we delegate to know.
【讨论】:
以上是关于C++中的回调函数的主要内容,如果未能解决你的问题,请参考以下文章
如何从 C# 调用具有 void* 回调和对象参数的 C++ Dll 中的函数