11

在 C++ 标准模板库中,有一个“功能”部分,其中许多类都重载了它们的()运算符。

在 C++ 中将函数用作对象是否带来了便利?

为什么我们不能只使用函数指针呢?有什么例子吗?

4

4 回答 4

10

当然,总是可以使用函数指针而不是函数对象,但是函数对象比函数指针提供了某些优点,即:

  • 更好的性能:

最明显和最重要的优势之一是它们更有可能产生更好的性能。在函数对象的情况下,在编译时可以获得更多详细信息,以便编译器可以准确地确定并因此内联要调用的函数,这与函数指针的情况不同,在函数指针的情况下,指针的取消引用使编译器难以确定实际函数那将被调用。

  • 函数对象是智能函数:

函数对象可能具有其他成员函数和属性。这意味着函数对象具有状态。实际上,同一个函数,由一个函数对象表示,可能同时具有不同的状态。这对于普通功能是不可能的。函数对象的另一个优点是您可以在使用/调用它们之前在运行时初始化它们。

  • 泛型编程的力量:

普通函数只有在签名不同时才能有不同的类型。但是,即使它们的签名相同,函数对象也可以具有不同的类型。实际上,函数对象定义的每个函数行为都有自己的类型。这是使用模板进行泛型编程的一项重大改进,因为可以将功能行为作为模板参数传递。

于 2012-03-09T02:42:42.540 回答
4

为什么我们不能只使用函数指针呢?有什么例子吗?

使用 C 风格的函数指针不能利用内联的优势。函数指针通常需要额外的间接查找。
但是,如果operator ()重载,那么编译器很容易编译inline代码并节省额外的调用,从而提高性能

重载的另一个优点operator ()是,可以设计一个隐含地函数对象视为参数的函数;无需将其作为单独的函数传递。更少的手工编码程序,更少的错误和更好的可读性。

Bjarne Stroustrup(C++ 发明者)网页上的这个问题很好地解释了这个方面。

如果需要, C++ 标准(模板)库使用带有重载的函数式编程operator ()

于 2012-03-09T02:45:25.293 回答
3

> 在 C++ 中使用函数作为对象是否带来了便利?

是:C++ 模板机制允许所有其他 C/C++ 编程风格(C 风格和 OOP 风格,见下文)。

> 为什么我们不能只使用函数指针呢?有什么例子吗?

但是我们可以:一个简单的 C 函数指针也是一个具有良好定义的 operator() 的对象。如果我们设计一个库,我们不想强迫任何人在不需要时使用那种 C 指针样式。这通常与强迫一切/每个人都采用/使用 OOP 风格一样不受欢迎;见下文。


从 C 程序员和函数式程序员的角度来看,OOP 不仅速度更慢而且更冗长,并且在大多数情况下是错误的抽象方向(“信息”不是也不应该是“对象”)。因此,每当在其他上下文中使用“对象”一词时,人们往往会感到困惑。

在 C++ 中,任何具有所需属性的东西都可以被视为对象。在这种情况下,一个简单的 C 函数指针也是一个对象。这并不意味着在不需要时使用 OOP 范式;这只是使用模板机制的正确方法。


要了解性能差异,请比较编程(语言)风格/范式及其可能的优化:

C风格:

  • 带有闭包的函数指针(OOP 中的“this”,指向某个结构的指针)作为第一个参数。
  • 要调用函数,首先需要访问函数的地址。
  • 那是1个间接;不可能内联。

C++(和 Java)OOP 风格:

  • 对从具有虚函数的类派生的对象的引用。
  • 参考是第一个指针。
  • 指向虚拟表的指针是第二个指针。
  • 虚拟表中的函数指针是第三个指针。
  • 那是3个间接;不可能内联。

C++ 模板样式:

  • 使用 () 函数复制对象。
  • 没有虚拟表,因为该对象的类型在编译时是已知的。
  • 函数的地址在编译时是已知的。
  • 那是0次间接;内联可能。

C++ 模板的通用性足以支持上述其他两种样式,并且在内联的情况下它们甚至可以胜过……</p>

编译的函数式语言:(由于缺少“正确的尾调用”,不包括 JVM 和 Javascript 作为目标平台)

  • 函数指针和对其在机器寄存器中的闭包的引用。
  • 它通常不是函数“调用”,而是像跳转一样的 GOTO。
  • 函数不需要堆栈,没有地址可以跳回,堆栈上没有参数或局部变量。
  • 函数有它们的垃圾回收闭包,其中包含参数和指向下一个要调用的函数的指针。
  • 为了让 CPU 预测跳转,需要尽早将函数的地址加载到寄存器中。
  • 那是 1 间接可能的跳跃预测;一切都几乎和内联一样快。
于 2012-03-09T17:22:11.567 回答
2

主要区别在于函数对象比普通函数指针更强大,因为它们可以保持状态。大多数算法采用模板函数而不是普通函数指针,这使得强大的构造可以用作绑定器,通过用存储在仿函数或C++11中更新的lambdas上的值填充额外的参数来调用具有不同签名的函数。一旦算法被设计为采用函子,在库中提供一组预定义的通用函数对象就很有意义。

除此之外,还有一些潜在的优势,在大多数情况下,这些仿函数是简单的类,编译器对其有完整的定义,并且可以执行函数调用的内联以提高性能。这就是为什么可以比C 库std::sort快得多的原因。qsort

于 2012-03-09T02:51:45.607 回答