我经常听到很多关于c++中的函子。谁能给我一个概述,他们是什么,在什么情况下他们会有用?


当前回答

这是一个我被迫使用Functor来解决我的问题的实际情况:

我有一组函数(比如20个),它们都是相同的,除了每个函数在3个特定位置调用不同的特定函数。

这是难以置信的浪费和代码复制。通常我会传入一个函数指针,然后在3个位置调用它。(所以代码只需要出现一次,而不是20次。)

但后来我意识到,在每种情况下,特定的功能需要完全不同的参数配置文件!有时2个参数,有时5个参数,等等。

另一种解决方案是有一个基类,其中特定的函数是派生类中的重写方法。但是我真的想要构建所有这些INHERITANCE,只是为了传递一个函数指针????

解决方案:所以我所做的是,我做了一个包装类(一个“Functor”),它能够调用任何我需要调用的函数。我提前设置了它(用它的参数等),然后我传递它而不是函数指针。现在调用的代码可以触发Functor,而不知道内部发生了什么。它甚至可以多次调用它(我需要它调用3次)。


这就是一个实际的例子,在这个例子中,Functor被证明是一个明显而简单的解决方案,它允许我将代码重复从20个函数减少到1个。

其他回答

就像其他人提到的,函子是一个像函数一样工作的对象,即它重载函数调用操作符。

函子通常用于STL算法。它们很有用,因为它们可以在函数调用之前和之间保持状态,就像函数语言中的闭包一样。例如,你可以定义一个MultiplyBy函子,将它的参数乘以一个指定的量:

class MultiplyBy {
private:
    int factor;

public:
    MultiplyBy(int x) : factor(x) {
    }

    int operator () (int other) const {
        return factor * other;
    }
};

然后你可以传递一个MultiplyBy对象给一个像std::transform:这样的算法:

int array[5] = {1, 2, 3, 4, 5};
std::transform(array, array + 5, array, MultiplyBy(3));
// Now, array is {3, 6, 9, 12, 15}

函子相对于指向函数的指针的另一个优点是可以在更多情况下内联调用。如果你将一个函数指针传递给transform,除非该调用被内联,并且编译器知道你总是将同一个函数传递给它,否则它不能通过指针内联调用。

将函数作为函子实现的一个很大的优点是,它们可以在调用之间维护和重用状态。例如,许多动态规划算法,如用于计算字符串之间的Levenshtein距离的Wagner-Fischer算法,都是通过填充一个大的结果表来工作的。每次调用函数时分配这个表的效率非常低,因此将函数作为函子实现并将表作为成员变量可以极大地提高性能。

下面是一个将Wagner-Fischer算法实现为函子的示例。注意表是如何在构造函数中分配,然后在operator()中重用的,并根据需要调整大小。

#include <string>
#include <vector>
#include <algorithm>

template <typename T>
T min3(const T& a, const T& b, const T& c)
{
   return std::min(std::min(a, b), c);
}

class levenshtein_distance 
{
    mutable std::vector<std::vector<unsigned int> > matrix_;

public:
    explicit levenshtein_distance(size_t initial_size = 8)
        : matrix_(initial_size, std::vector<unsigned int>(initial_size))
    {
    }

    unsigned int operator()(const std::string& s, const std::string& t) const
    {
        const size_t m = s.size();
        const size_t n = t.size();
        // The distance between a string and the empty string is the string's length
        if (m == 0) {
            return n;
        }
        if (n == 0) {
            return m;
        }
        // Size the matrix as necessary
        if (matrix_.size() < m + 1) {
            matrix_.resize(m + 1, matrix_[0]);
        }
        if (matrix_[0].size() < n + 1) {
            for (auto& mat : matrix_) {
                mat.resize(n + 1);
            }
        }
        // The top row and left column are prefixes that can be reached by
        // insertions and deletions alone
        unsigned int i, j;
        for (i = 1;  i <= m; ++i) {
            matrix_[i][0] = i;
        }
        for (j = 1; j <= n; ++j) {
            matrix_[0][j] = j;
        }
        // Fill in the rest of the matrix
        for (j = 1; j <= n; ++j) {
            for (i = 1; i <= m; ++i) {
                unsigned int substitution_cost = s[i - 1] == t[j - 1] ? 0 : 1;
                matrix_[i][j] =
                    min3(matrix_[i - 1][j] + 1,                 // Deletion
                    matrix_[i][j - 1] + 1,                      // Insertion
                    matrix_[i - 1][j - 1] + substitution_cost); // Substitution
            }
        }
        return matrix_[m][n];
    }
};

函子是一个类似于函数的对象。 基本上,一个定义operator()的类。

class MyFunctor
{
   public:
     int operator()(int x) { return x * 2;}
}

MyFunctor doubler;
int x = doubler(5);

真正的优点是函子可以保存状态。

class Matcher
{
   int target;
   public:
     Matcher(int m) : target(m) {}
     bool operator()(int x) { return x == target;}
}

Matcher Is5(5);

if (Is5(n))    // same as if (n == 5)
{ ....}

函子基本上就是一个定义操作符()的类。这让你可以创建“看起来像”函数的对象:

// this is a functor
struct add_x {
  add_x(int val) : x(val) {}  // Constructor
  int operator()(int y) const { return x + y; }

private:
  int x;
};

// Now you can use it like this:
add_x add42(42); // create an instance of the functor class
int i = add42(8); // and "call" it
assert(i == 50); // and it added 42 to its argument

std::vector<int> in; // assume this contains a bunch of values)
std::vector<int> out(in.size());
// Pass a functor to std::transform, which calls the functor on every element 
// in the input sequence, and stores the result to the output sequence
std::transform(in.begin(), in.end(), out.begin(), add_x(1)); 
assert(out[i] == in[i] + 1); // for all i

函子有几个优点。其一,与常规函数不同,它们可以包含状态。上面的例子创建了一个函数,无论你给它什么,它都会加上42。但是值42并不是硬编码的,它是在创建函数实例时作为构造函数参数指定的。我可以创建另一个加法器,只需要用不同的值调用构造函数,就可以加27。这使得它们可以很好地定制。

As the last lines show, you often pass functors as arguments to other functions such as std::transform or the other standard library algorithms. You could do the same with a regular function pointer except, as I said above, functors can be "customized" because they contain state, making them more flexible (If I wanted to use a function pointer, I'd have to write a function which added exactly 1 to its argument. The functor is general, and adds whatever you initialized it with), and they are also potentially more efficient. In the above example, the compiler knows exactly which function std::transform should call. It should call add_x::operator(). That means it can inline that function call. And that makes it just as efficient as if I had manually called the function on each value of the vector.

如果我传递的是一个函数指针,编译器不能立即看到它指向哪个函数,所以除非它执行一些相当复杂的全局优化,否则它必须在运行时解除对指针的引用,然后进行调用。

这是一个我被迫使用Functor来解决我的问题的实际情况:

我有一组函数(比如20个),它们都是相同的,除了每个函数在3个特定位置调用不同的特定函数。

这是难以置信的浪费和代码复制。通常我会传入一个函数指针,然后在3个位置调用它。(所以代码只需要出现一次,而不是20次。)

但后来我意识到,在每种情况下,特定的功能需要完全不同的参数配置文件!有时2个参数,有时5个参数,等等。

另一种解决方案是有一个基类,其中特定的函数是派生类中的重写方法。但是我真的想要构建所有这些INHERITANCE,只是为了传递一个函数指针????

解决方案:所以我所做的是,我做了一个包装类(一个“Functor”),它能够调用任何我需要调用的函数。我提前设置了它(用它的参数等),然后我传递它而不是函数指针。现在调用的代码可以触发Functor,而不知道内部发生了什么。它甚至可以多次调用它(我需要它调用3次)。


这就是一个实际的例子,在这个例子中,Functor被证明是一个明显而简单的解决方案,它允许我将代码重复从20个函数减少到1个。