使用function指针的好处

我已经编程了几年,并且在某些情况下使用了函数指针。 我想知道的是,出于性能原因何时使用它们是合适的,我的意思是在游戏环境中,而不是商业软件。

函数指针很快,John Carmack在Quake和Doom源代码中使用了它们,因为他是天才:)

我想更多地使用函数指针,但我想在最合适的地方使用它们。

现在,在C,C ++,C#和Java等现代c风格语言中,函数指针的最佳和最实用的用途是什么?

关于函数指针没有什么特别“快”的东西。 它们允许您调用在运行时指定的函数。 但是你有来自任何其他函数调用(加上额外的指针间接)的完全相同的开销。 此外,由于要在运行时确定要调用的函数,因此编译器通常不能像在其他任何地方那样内联函数调用。 因此,在某些情况下,函数指针可能比常规函数调用慢得多。

函数指针与性能无关,不应该用于获得性能。

相反,它们是对函数式编程范例的一种非常轻微的点头,因为它们允许您将函数作为参数传递或在另一个函数中返回值。

一个简单的例子是通用排序function。 它必须有一些方法来比较两个元素,以确定它们应该如何排序。 这可能是一个传递给sort函数的函数指针,实际上c ++的std :: sort()可以完全像这样使用。 如果要求它对未定义less运算符的类型的序列进行排序,则必须传入一个可以调用的函数指针来执行比较。

这使我们很好地找到了一个更好的选择。 在C ++中,您不仅限于函数指针。 您经常使用仿函数 – 即,重载operator()的类,以便它们可以被“调用”,就像它们是函数一样。 与函数指针相比​​,函子有几个很大的优点:

  • 它们提供了更大的灵活性:它们是完整的类,包含构造函数,析构函数和成员变量。 它们可以维护状态,并且它们可以暴露周围代码可以调用的其他成员函数。
  • 它们更快:与函数指针不同,函数指针的类型只编码函数的签名( void (*)(int)类型的变量可以是任何接受int并返回void的函数。我们无法知道哪一个),仿函数的类型编码应该调用的精确函数(因为仿函数是一个类,称之为C,我们知道要调用的函数是,并且将始终是C :: operator())。 这意味着编译器可以内联函数调用。 这就是使通用std :: sort与专为您的数据类型设计的手动编码排序function一样快的神奇之处。 编译器可以消除调用用户定义函数的所有开销。
  • 它们更安全:函数指针中的类型安全性非常小。 您无法保证它指向有效的function。 它可能是NULL。 指针的大多数问题也适用于函数指针。 它们很危险且容易出错。

函数指针(在C中)或仿函数(在C ++中)或委托(在C#中)都解决了同样的问题,具有不同的优雅和灵活性:它们允许您将函数视为一等值,将它们传递给您任何其他变量。 您可以将函数传递给另一个函数,它将在指定的时间调用您的函数(当计时器到期时,窗口需要重绘时,或者需要比较数组中的两个元素时)

据我所知(我可能错了,因为我多年没有使用过Java),Java没有直接的等价物。 相反,您必须创建一个实现接口的类,并定义一个函数(例如,将其称为Execute())。 然后调用foo.Execute()而不是调用用户提供的函数(以函数指针,仿函数或委托的forms)。 与原则上的C ++实现类似,但没有C ++模板的通用性,并且没有允许您以相同方式处理函数指针和函子的函数语法。

这就是你使用函数指针的地方:当更复杂的替代品不可用时(即你被困在C中),你需要将一个函数传递给另一个函数。 最常见的情况是回调。 您可以定义希望系统在X发生时调用的函数F. 因此,您创建一个指向F的函数指针,并将其传递给相关系统。

所以真的,忘了约翰卡马克,不要以为你在他的代码中看到的任何内容都会让你的代码变得更好。 他使用了函数指针,因为你提到的游戏是用C编写的,其中没有优秀的替代品,而不是因为它们是一些神奇的成分,它们的存在使代码运行得更快。

如果您在运行时之前不知道目标平台支持的function(例如CPUfunction,可用内存),它们将非常有用。 显而易见的解决方案是编写如下函数:

 int MyFunc() { if(SomeFunctionalityCheck()) { ... } else { ... } } 

如果在重要循环内部调用此函数,那么最好使用MyFunc的函数指针:

 int (*MyFunc)() = MyFunc_Default; int MyFunc_SomeFunctionality() { // if(SomeFunctionalityCheck()) .. } int MyFunc_Default() { // else ... } int MyFuncInit() { if(SomeFunctionalityCheck()) MyFunc = MyFunc_SomeFunctionality; } 

当然还有其他用途,如回调函数 ,从内存执行字节代码或创建解释语言。

在Windows上执行Intel兼容的字节代码 ,这可能对解释器有用。 例如,这是一个stdcall函数返回42(0x2A)存储在一个可以执行的数组中:

 code = static_cast(VirtualAlloc(0, 6, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE)); // mov eax, 42 code[0] = 0x8b; code[1] = 0x2a; code[2] = 0x00; code[3] = 0x00; code[4] = 0x00; // ret code[5] = 0xc3; // this line executes the code in the byte array reinterpret_cast(code)(); ... VirtualFree(code, 6, MEM_RELEASE); 

);

无论何时在C#中使用事件处理程序或委托,您实际上都在使用函数指针。

不,他们不是速度。 函数指针是关于方便的。

乔纳森

这些天在现代c风格语言中整数的最佳和最实用的用途是什么?

在许多情况下,函数指针用作回调。 一种用途是作为排序算法中的比较函数。 因此,如果您要尝试比较自定义对象,则可以提供指向知道如何处理该数据的比较函数的函数指针。

那就是说,我将从我以前的一位教授那里得到一句话:

对待一个新的C ++function,就像你在一个拥挤的房间里对待一个装载的自动武器:永远不要使用它只是因为它看起来很漂亮。 等到你明白后果,不要变得可爱,写下你所知道的,并知道你写的是什么。

在C ++之前的昏暗,黑暗时代,我在我的代码中使用了一种常见的模式,即用一组函数指针定义一个结构,该结构通常以某种方式对该结构进行操作并为其提供特定的行为。 用C ++术语来说,我只是在构建一个vtable。 不同之处在于,我可以在运行时对结构进行副作用,以根据需要动态更改单个对象的行为。 这提供了更丰富的inheritance模型,但代价是稳定性和易于调试。 然而,最大的代价是,只有一个人能够有效地编写这些代码:我。

我在UI框架中大量使用了这个,让我可以改变绘制对象的方式,命令的目标,等等,即时提供的UI很少。

以OO语言forms化这个过程在每一个有意义的方面都会更好。

刚谈到C#,但是函数指针遍布C#。 委托和事件(以及Lambdas等)都是引擎盖下的函数指针,因此几乎任何C#项目都会充斥着函数指针。 基本上每个LINQ查询附近的每个事件处理程序都将使用函数指针。

有时使用函数指针可以加快处理速度。 可以使用简单的分派表而不是长的switch语句或if-then-else序列。

函数指针是穷人尝试运行的function。 您甚至可以创建一个参数,让函数指针使语言起作用,因为您可以使用它们编写更高阶的函数。

如果没有闭包和简单的语法,它们就会变得非常糟糕。 所以你倾向于使用它们远远不如欲望。 主要用于“回调”function。

有时,OO设计通过创建整个接口类型来传递所需的function,从而使用函数。

C#有闭包,所以函数指针(实际上存储一个对象,所以它不仅仅是一个原始函数,而且也是类型状态)在那里更加实用。

编辑其中一条评论说应该用函数指针演示高阶函数。 任何采用回调函数的函数都是高阶函数。 比方说,就像EnumWindows :

 BOOL EnumWindows( WNDENUMPROC lpEnumFunc, LPARAM lParam ); 

第一个参数是传入的函数,很容易。 但由于C中没有闭包,我们得到了这个可爱的第二个参数:“指定要传递给回调函数的应用程序定义的值。” 该应用程序定义的值允许您手动传递无类型状态以补偿缺少闭包。

.NET框架也充满了类似的设计。 例如, IAsyncResult .AsyncState:“获取一个用户定义的对象,该对象限定或包含有关异步操作的信息。” 由于IAR是你所有的回调,没有闭包,你需要一种方法将一些数据推送到异步操作中,以便以后可以将其抛出。

根据我个人的经验,他们可以帮助您节省大量代码。

考虑一下条件:{

 switch(sample_var) { case 0: func1(); break; case 1: func2(); break; up to case n: funcn(); break; 

}

其中func1()… funcn()是具有相同原型的函数。 我们能做的是:声明一个函数指针数组arrFuncPoint包含函数func1()funcn()的地址

然后整个开关盒将被替换

* arrFuncPoint [sample_var];

函数指针很快

在什么情况下? 相比?

听起来你只是想使用它们来使用函数指针。 那会很糟糕。

指向函数的指针通常用作回调或事件处理程序。