回调函数的应用

时间:2012-03-06 13:24:43

标签: c++ c callback polymorphism

我想知道实际使用callback functions的位置?

有没有办法在C / C ++中实现回调函数(除了使用函数指针)?

请解释以下内容 -

A callback can be used as a simpler alternative to polymorphism and generic programming

由于

4 个答案:

答案 0 :(得分:6)

  

我想知道实际使用回调函数的位置?

两个最常见的用途是允许用户实现算法的一部分(例如,传递给C的qsort和C ++的std::sort的比较器允许对任意类型进行排序),并且接收外部事件的通知(例如,在许多异步用户界面和通信库中,例如Boost.Asio)。

  

有没有办法在C / C ++中实现回调函数(除了使用函数指针)?

没有C / C ++这样的语言。

在C中,唯一合理的机制是函数指针。

在C ++中有很多方法。对于那些喜欢OOP的人来说,一种常见的方法是定义一个抽象接口类。在那里声明的虚函数充当回调,并且可以调用派生实现类中定义的任意代码。

那些喜欢泛型和函数式编程的人会改为定义一个重载operator()的类 - 通常称为“functor”或“function object”。这样的东西可以像函数一样调用,但也可以随身携带状态。此模式在整个标准库中广泛使用,例如为容器和自定义比较器提供自定义分配器以进行排序算法。

这两种方法都可以更方便地为回调提供额外的信息 - 可能是一个调用成员函数的对象,或一个用于比较函数参数的值。在C中,任何这样的“用户”状态都必须通过回调中的额外参数传递 - 如果回调机制的设计者忽略了包含它,那么使用它会非常棘手。

C ++ 11为处理仿函数提供了一些方便的新方法。 std::function是一种多态类型,可以表示任何类似函数的东西,如果你不想模板化函数类型,它是一个很好用于回调的类型。 std::bind和lambda表达式是从一些任意代码中定义仿函数的简单方法。

  

回调可以用作多态和泛型编程的简单替代方法

脱离背景,这没有多大意义。我想这意味着,如果你使用函数指针进行回调,则不需要定义任何抽象接口或仿函数。如果他们认为多态性和通用性很复杂,有些人可能会认为这更简单;但是它也带来了它自己的复杂性,因为没有类型安全的方法将任意用户数据传递给回调,就像使用C ++方法一样。

答案 1 :(得分:1)

一个例子是qsort。您传递回调函数以确定元素的排序。

我不确定回调如何比多态更简单,除非你在C中编码了50年并且从未使用过OOP。多态性是一个中心OOP概念,不仅与C ++有关,而且并非所有OOP语言都有函数指针。

但确实如此,在纯C中你可以通过函数指针模拟多态性。

答案 2 :(得分:0)

  

我想知道实际使用回调函数的位置?

GUI programming。在Windows平台上,当您想要创建窗口时,您将回调函数传递给控制窗口行为的系统。当然,高级框架会隐藏这种行为,但它仍然在这里。

Multithreading,异步IO,当你想使用用户定义的函数进行系统处理时(例如qsort)。

基本上,如果你想制作应该与多种语言交互的可扩展代码,你就不能没有回调。

答案 3 :(得分:0)

回调函数的含义是:程序员在代码中的任何地方都不会调用,而是从外部源调用的函数。通常,这意味着操作系统。

  • 如果调用该函数的外部源是硬件,则被调用函数被命名为“中断服务例程”。
  • 如果外部源是软件,则被调用的函数名为“回调函数”。

例如,您将在Windows编程中找到大量回调函数。每个图形窗口都有一个回调函数“WindowProc”,它接收来自OS的调用,用于窗口上发生的每个动作。然后由应用程序员编写回调函数并处理他们感兴趣的事件。他们不感兴趣的事件将被传递给默认处理程序(或基类,如果你愿意的话)。

如果程序员想要在窗口上单击鼠标时发生特殊情况,他们可以通过用自己的代码替换默认行为来覆盖鼠标单击的默认行为。这样就可以实现多态性,即使在C程序中也是如此。

Windows编程示例:

LRESULT CALLBACK WindowProc (HWND hwnd, UINT umsg, WPARAM wparam, LPARAM lparam)
{
  switch(umsg)
  {
    case WM_LBUTTONDOWN:

      do_something(); /* overwrite default behavior of a left mouse button click */

      break; /* break = and then execute the default behavior afterwards */

    case WM_RBUTTONDOWN:

      do_something(); /* overwrite default behavior of a right mouse button click */

      return 0; /* overwrite the default behavior entirely */

  }    


  // execute default behavior for the event:
  return DefWindowProc(hwnd, umsg, wparam, lparam);
}

switch语句中的break类似于plain inheritage:执行继承的函数,然后执行基类函数。虽然return 0;类似于多态/虚拟继承,但由于继承的对象完全覆盖了默认行为。


旁注:由于回调函数是从外部源而不是程序本身调用的,因此一些dumber编译器会假设函数永远不会被调用,因此执行危险的优化。

例如,如果从上面的WindowProc修改了全局变量“flag”,并且main()中的代码依赖于“flag”,编译器可能会优化main()中的代码,因为它认为“旗帜”从未在任何地方使用过。

为了避免这种不正确的编译器行为,优良作法是始终将回调函数和程序其余部分之间共享的所有变量声明为volatile,这将阻止该变量的所有优化。