我经常听到很多关于c++中的函子。谁能给我一个概述,他们是什么,在什么情况下他们会有用?
当前回答
函子是一种高阶函数,它将函数应用于参数化(即模板化)类型。它是映射高阶函数的推广。例如,我们可以像这样为std::vector定义一个函子:
template<class F, class T, class U=decltype(std::declval<F>()(std::declval<T>()))>
std::vector<U> fmap(F f, const std::vector<T>& vec)
{
std::vector<U> result;
std::transform(vec.begin(), vec.end(), std::back_inserter(result), f);
return result;
}
这个函数接受一个std::vector<T>,并在给定一个接受T并返回U的函数F时返回std::vector<U>。一个函子不一定要在容器类型上定义,它也可以为任何模板类型定义,包括std::shared_ptr:
template<class F, class T, class U=decltype(std::declval<F>()(std::declval<T>()))>
std::shared_ptr<U> fmap(F f, const std::shared_ptr<T>& p)
{
if (p == nullptr) return nullptr;
else return std::shared_ptr<U>(new U(f(*p)));
}
下面是一个将类型转换为double类型的简单示例:
double to_double(int x)
{
return x;
}
std::shared_ptr<int> i(new int(3));
std::shared_ptr<double> d = fmap(to_double, i);
std::vector<int> is = { 1, 2, 3 };
std::vector<double> ds = fmap(to_double, is);
函子应该遵循两条定律。第一个是恒等定律,它指出,如果函子给定了恒等函数,它应该与将恒等函数应用于类型相同,即fmap(identity, x)应该与identity(x)相同:
struct identity_f
{
template<class T>
T operator()(T x) const
{
return x;
}
};
identity_f identity = {};
std::vector<int> is = { 1, 2, 3 };
// These two statements should be equivalent.
// is1 should equal is2
std::vector<int> is1 = fmap(identity, is);
std::vector<int> is2 = identity(is);
下一个定律是组合定律,它指出,如果函子被赋予两个函数的组合,它应该与将函子应用于第一个函数,然后再应用于第二个函数相同。因此,fmap(std::bind(f, std::bind(g, _1)), x)应该与fmap(f, fmap(g, x))相同:
double to_double(int x)
{
return x;
}
struct foo
{
double x;
};
foo to_foo(double x)
{
foo r;
r.x = x;
return r;
}
std::vector<int> is = { 1, 2, 3 };
// These two statements should be equivalent.
// is1 should equal is2
std::vector<foo> is1 = fmap(std::bind(to_foo, std::bind(to_double, _1)), is);
std::vector<foo> is2 = fmap(to_foo, fmap(to_double, is));
其他回答
除了在回调中使用,c++函子还可以帮助为矩阵类提供Matlab喜欢的访问样式。这里有一个例子。
早在c++出现之前,“functor”这个名字就已经在范畴理论中传统地使用了。这与c++中函子的概念无关。最好使用name function object,而不是c++中所谓的“functor”。这就是其他编程语言调用类似结构的方式。
用于代替普通函数:
特点:
函数对象可以有状态 函数对象适合OOP(它的行为和其他对象一样)。
缺点:
给程序带来了更多的复杂性。
用于代替函数指针:
特点:
函数对象通常可以内联
缺点:
函数对象不能在运行时与其他函数对象类型交换(至少除非它扩展了一些基类,因此会产生一些开销)
用于代替虚函数:
特点:
函数对象(非虚拟)不需要虚表和运行时调度,因此在大多数情况下更有效
缺点:
函数对象不能在运行时与其他函数对象类型交换(至少除非它扩展了一些基类,因此会产生一些开销)
函子是一种高阶函数,它将函数应用于参数化(即模板化)类型。它是映射高阶函数的推广。例如,我们可以像这样为std::vector定义一个函子:
template<class F, class T, class U=decltype(std::declval<F>()(std::declval<T>()))>
std::vector<U> fmap(F f, const std::vector<T>& vec)
{
std::vector<U> result;
std::transform(vec.begin(), vec.end(), std::back_inserter(result), f);
return result;
}
这个函数接受一个std::vector<T>,并在给定一个接受T并返回U的函数F时返回std::vector<U>。一个函子不一定要在容器类型上定义,它也可以为任何模板类型定义,包括std::shared_ptr:
template<class F, class T, class U=decltype(std::declval<F>()(std::declval<T>()))>
std::shared_ptr<U> fmap(F f, const std::shared_ptr<T>& p)
{
if (p == nullptr) return nullptr;
else return std::shared_ptr<U>(new U(f(*p)));
}
下面是一个将类型转换为double类型的简单示例:
double to_double(int x)
{
return x;
}
std::shared_ptr<int> i(new int(3));
std::shared_ptr<double> d = fmap(to_double, i);
std::vector<int> is = { 1, 2, 3 };
std::vector<double> ds = fmap(to_double, is);
函子应该遵循两条定律。第一个是恒等定律,它指出,如果函子给定了恒等函数,它应该与将恒等函数应用于类型相同,即fmap(identity, x)应该与identity(x)相同:
struct identity_f
{
template<class T>
T operator()(T x) const
{
return x;
}
};
identity_f identity = {};
std::vector<int> is = { 1, 2, 3 };
// These two statements should be equivalent.
// is1 should equal is2
std::vector<int> is1 = fmap(identity, is);
std::vector<int> is2 = identity(is);
下一个定律是组合定律,它指出,如果函子被赋予两个函数的组合,它应该与将函子应用于第一个函数,然后再应用于第二个函数相同。因此,fmap(std::bind(f, std::bind(g, _1)), x)应该与fmap(f, fmap(g, x))相同:
double to_double(int x)
{
return x;
}
struct foo
{
double x;
};
foo to_foo(double x)
{
foo r;
r.x = x;
return r;
}
std::vector<int> is = { 1, 2, 3 };
// These two statements should be equivalent.
// is1 should equal is2
std::vector<foo> is1 = fmap(std::bind(to_foo, std::bind(to_double, _1)), is);
std::vector<foo> is2 = fmap(to_foo, fmap(to_double, is));
就像其他人提到的,函子是一个像函数一样工作的对象,即它重载函数调用操作符。
函子通常用于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,除非该调用被内联,并且编译器知道你总是将同一个函数传递给它,否则它不能通过指针内联调用。
函子也可以用来模拟在函数中定义局部函数。参考这个问题和另一个问题。
但是局部函子不能访问外部的自动变量。lambda (c++ 11)函数是一个更好的解决方案。