只有 operator() 的结构和普通函数之间的实际区别

eva*_*cox 6 c++ c++11 c++14

我见过这样的代码:

struct foo_functor {
  template <typename T, typename U>
  constexpr auto operator()(T t, U u) const -> decltype(t | u) {
    return t | u;
  }
};

constexpr foo_functor foo;
Run Code Online (Sandbox Code Playgroud)

据我所知,它与以下内容相同:

template <typename T, typename U>
constexpr auto foo(T t, U u) -> decltype(t | u) {
  return t | u;
}
Run Code Online (Sandbox Code Playgroud)

为什么要做第一个?有什么区别吗?据我从编译器输出中看到,至少在constexpr,没有。如果不是constexpr,那会不会有什么不同呢?

编辑:请注意,与第一个示例非常相似的代码似乎被用来代替普通函数。6 种不同的结构,都只有operator()模板,都像示例的最后一行一样被实例化。然后每个都像正常功能一样使用。

Bri*_*ian 5

有人在评论中建议函数对象可以有额外的状态。虽然这是真的,但我会更具体一点:您可以创建具有不同状态的函数对象的多个副本。如果函数对象是单例,那么这点就没有实际意义;函数也可以具有全局变量形式的状态。

如果你的函数对象被声明constexpr,那么它的内部状态都不能是可变的。这将它置于与constexpr函数相同的位置:调用它可以是一个常量表达式,但前提是它不访问任何非常量表达式全局状态。

C++17 之前的一个重要区别是函数可以是inline,而对象不能是。在 C++14 中,如果您foo在头文件中定义函子,那么每个翻译单元将有一个它的副本。如果需要foo在所有翻译单元中使用相同的地址,则需要将其声明为inline函数。但在 C++17 中,函数对象也可以内联。

但是即使你只有一个函数对象的实例,并且它没有状态,而且你使用的是 C++17 或更高版本,它和函数之间至少还有一个重要的区别:函数可以通过参数相关查找,而函数对象不能。这就是为什么 C++20 Ranges 库中的一些“函数”实际上根本不能是函数,而必须是函数对象的原因。这些被非正式地称为niebloids

  • 另一种情况:当作为参数传递给函数时,函数变为“ReturnType (*ptr)(ParamType)”并通过指针传递,编译器可能在内联它时遇到问题。Functoid 作为 `foo_functor` 传递,因此编译器确切地知道将调用哪个函数,并且内联它没有问题。这可以提高性能。 (3认同)