c++中的回调函数

在c++中,何时以及如何使用回调函数?

< p > 编辑: < br > 我想看一个简单的例子来写一个回调函数
589921 次浏览

回调函数是一个被传递到例程中的方法,并在某个时刻被传递给它的例程调用。

这对于开发可重用软件非常有用。例如,许多操作系统API(如Windows API)大量使用回调。

例如,如果你想处理文件夹中的文件,你可以用你自己的例程调用一个API函数,并且你的例程在指定的文件夹中对每个文件运行一次。这使得API非常灵活。

在c++中并没有明确的回调函数的概念。回调机制通常通过函数指针、函子对象或回调对象实现。程序员必须显式地设计和实现回调功能。

根据反馈进行编辑:

尽管这个答案收到了负面的反馈,但它并没有错。我会试着更好地解释我的观点。

C和c++拥有实现回调函数所需的一切。实现回调函数最常见和最简单的方法是将函数指针作为函数参数传递。

然而,回调函数和函数指针并不是同义词。函数指针是一种语言机制,而回调函数是一个语义概念。函数指针不是实现回调函数的唯一方法——你还可以使用函子甚至各种各样的虚函数。使函数调用成为回调的不是用于识别和调用函数的机制,而是调用的上下文和语义。将某个东西称为回调函数意味着调用函数和被调用的特定函数之间存在比正常情况下更大的分离,调用方和被调用方之间存在更松散的概念耦合,调用方对被调用的内容具有显式控制。正是这种松散的概念耦合和调用者驱动的函数选择的模糊概念使某些东西成为回调函数,而不是函数指针的使用。

例如,IFormatProvider的. net文档说GetFormat是一个回调方法,即使它只是一个普通的接口方法。我认为没有人会认为所有的虚方法调用都是回调函数。使GetFormat成为回调方法的不是它如何传递或调用的机制,而是调用方选择调用哪个对象的GetFormat方法的语义。

一些语言包含带有显式回调语义的特性,通常与事件和事件处理相关。例如,c#有事件类型,其语法和语义显式地围绕回调概念设计。Visual Basic有它的处理子句,它显式地将一个方法声明为回调函数,同时抽象了委托或函数指针的概念。在这些情况下,回调的语义概念集成到语言本身中。

另一方面,C和c++几乎不显式地嵌入回调函数的语义概念。机制在那里,集成的语义不在那里。你可以很好地实现回调函数,但是为了获得更复杂的包含显式回调语义的东西,你必须在c++提供的基础上构建它,比如Qt对信号和槽位所做的。

简而言之,c++拥有实现回调所需的一切,通常使用函数指针非常简单。它没有的是语义特定于回调的关键字和特性,比如提高发出处理事件+ =等。如果您来自具有这些类型的元素的语言,那么c++中的本机回调支持将感觉很中性。

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++回调函数的“现代”方式。

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);
}

回调函数是C标准的一部分,因此也是c++的一部分。但如果你正在使用c++,我建议你使用观察者模式代替:http://en.wikipedia.org/wiki/Observer_pattern

请参阅上面的定义,其中声明将回调函数传递给其他函数,并在某个时刻调用它。

在c++中,让回调函数调用类方法是可取的。当您这样做时,您可以访问成员数据。如果你使用C语言定义回调函数,你必须将它指向一个静态成员函数。这不是很理想。

下面是如何在c++中使用回调函数。假设有4个文件。每个类都有一对。cpp /. h文件。类C1是具有我们想要回调的方法的类。C2回调C1的方法。在这个例子中,回调函数有一个参数,我为读者添加了这个参数。这个例子没有显示任何被实例化和使用的对象。这种实现的一个用例是,有一个类读取数据并将其存储到临时空间,而另一个类则对数据进行后期处理。使用回调函数,对于读取的每一行数据,回调函数都可以处理它。这种技术减少了所需临时空间的开销。它对于返回大量数据的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);
}

注意:大多数答案都涉及函数指针,这是在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

回调的另一个应用是某些事件的通知调用者,这使得一定程度的静态/编译时灵活性成为可能。

就我个人而言,我使用了一个使用两种不同回调的本地优化库:

  • 如果需要函数值和基于输入值向量的梯度,则调用第一个回调(逻辑回调:函数值确定/梯度推导)。
  • 第二个回调对每个算法步骤调用一次,并接收关于算法收敛的某些信息(通知回调)。
因此,库设计者并不负责决定如何处理提供给程序员的信息 通过通知回调,他不需要担心如何确定函数值因为它们是由逻辑回调提供的。正确处理这些事情是库用户的任务,并使库保持精简和更通用。< / p >

此外,回调可以启用动态运行时行为。

想象一下,有一种游戏引擎类,每当用户按下键盘上的一个按钮,就会触发一个函数,以及一组控制游戏行为的函数。 通过回调,你可以(重新)决定在运行时执行哪个操作
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)中可调用的是什么?

更正式的描述请参见cppreference上的c++概念:可调用

回调函数可以在c++(11)中以多种方式实现,因为有几种不同的东西最终都是可调用的*:

  • 函数指针(包括指向成员函数的指针)
  • std::function对象
  • Lambda表达式
  • 绑定表达式
  • 函数对象(具有重载函数调用操作符operator()的类)

* 注意:指向数据成员的指针也是可调用的,但根本不调用任何函数

详细编写回调的几个重要方法

  • X.1在本文中“编写”回调是指声明和命名回调类型的语法。
  • X.2“调用”回调是指调用这些对象的语法。
  • X.3“Using”回调是指使用回调将参数传递给函数时的语法。

注意:从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_ttypedef也可以写成:

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
}

回调使用符号和兼容类型

接受函数指针的回调函数可以使用函数指针调用。

使用一个接受函数指针回调的函数相当简单:

 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; }
};

将指针写入成员函数/类型表示法

某个类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成员函数的指针。 注意:需要括号!< / em >

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

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
}

回调使用符号和兼容类型

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 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_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 )

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;
}

公认的答案是非常有用和相当全面的。然而,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&)>,它将该函数描述为将调用它的函数的输入参数。

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;
};

公认的答案是全面的,但与问题有关,我只是想在这里放一个简单的例子。我有一个很久以前写的代码。我想以有序的方式遍历树(左节点然后根节点然后右节点),每当我到达一个节点,我希望能够调用任意函数,这样它就可以做任何事情。

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);
}

@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

我们可以将框架开发为order,但它对只想关心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.