许多标准库算法都采用谓词函数。但是,这些谓词的类型是用户提供的任意模板参数。为什么C ++ 11没有指定这些类型采用特定类型,例如std::function
?例如:
template< class InputIt >
InputIt find_if( InputIt first, InputIt last,
std::function<bool()> p );
是不是使用这个而不是模板作为参数类型而不是更干净?
答案 0 :(得分:10)
std::function
用于运行时多态。任何特定的std::function
实例都可以存储任何类型的仿函数(当然,这种类型适合std::function
的签名)。
标准库算法等根据其函数参数的类型进行模板化。因此,他们不需要运行时多态来完成他们的工作;它们依赖于编译时多态性。
最重要的是,此类算法不需要强制运行时多态性的 cost 。如果您想要运行时多态性,可以发送std::function
或其他任何内容。如果你想要编译时多态,你可以为它提供一个不使用多态调度的类型(又名:大多数函子或函数)。
运行时多态性的代价还包括无法内联函数调用。使用适当的函子(甚至函数指针,取决于你的编译器有多好),编译器通常可以根据需要内联函数调用。使用运行时多态性,您不仅要支付运行时调度的成本(可能包括额外的参数转发成本),而且还会失去重要的优化机会。
答案 1 :(得分:9)
性能!
模板基本功能非常好于std::function
模式。我为你做了这个测试:
template <typename F>
void test1(const F &f)
{
for (unsigned long long i = 0; i < 19000000; i++)
f();
}
void test2(function<void()> &f)
{
for (unsigned long long i = 0; i < 19000000; i++)
f();
}
int main()
{
{
LARGE_INTEGER frequency, start, end;
double interval;
QueryPerformanceFrequency(&frequency);
QueryPerformanceCounter(&start);
unsigned long long x = 0;
test1([&x]()
{
x++;
});
QueryPerformanceCounter(&end);
interval = (double) (end.QuadPart - start.QuadPart) / frequency.QuadPart;
cout << "Template mode: " << interval << " " << x << endl;
}
{
LARGE_INTEGER frequency, start, end;
double interval;
QueryPerformanceFrequency(&frequency);
QueryPerformanceCounter(&start);
unsigned long long x = 0;
function<void() > f = [&x]()
{
x++;
};
test2(f);
QueryPerformanceCounter(&end);
interval = (double) (end.QuadPart - start.QuadPart) / frequency.QuadPart;
cout << "std::function mode:" << interval << " " << x << endl;
}
}
Windows 7上的模板模式:2.13861e-006
std :: function mode:0.220006
gcc 4.7.2 -O2
Core2 Duo CPU 2.40GHz
答案 2 :(得分:4)
因为std::function
不完美。
它是如何不完美的?让我列举一下方法。
{
首先,std::function
并不支持传递给它的任意对象的完美支持。而且,在实践中,它不能。 std::function
向呼叫者公开一个固定签名,并且可以接受多种被调用者,并且完美转发需要为每个呼叫者和被呼叫者提供自定义签名。它确实支持完全转发完全它在签名中公开的参数,但这还不够。
想象一下std::function
有两个参数,int
和double
。要使其完美转发,必须接受int&
,int&&
和int const&
,这是double
的相同集合(更不用说它的易失性变量)。每个std::function
必须接受以完成转发的签名数量随着它拥有的参数数量呈指数增长。 std::function
它公开的签名集(当前为1)在实例化时是固定的,而它公开的签名模板集是无限的,只有在使用时才会生成。这很重要,因为一些类似函数的对象会针对这些情况进行不同的优化!因此,每个std::function
您已经删除了完全转发对包装类型的调用的机会。
std::function
不完美的第二个原因是因为编译器很糟糕。如果你在一个std::function
中包装一个lambda,然后用它调用一个算法,理论上编译器可以意识到这个std::function
正在包装一些固定的lambda - 但实际上,它会失去对这个事实的追踪,并将std::function
视为一个泛型类的包装器。因此,即使在std::function
的签名与算法的用例完全匹配的情况下,防止std::function
的类型瓶颈使转发不完善,实际上也会因为由std::function
执行的类型擦除,编译器会发现很难优化std::function
调用&#34;屏障&#34;。
std::function
不完美的第三个原因是它会鼓励算法编写者过度限制算法中可以传递的参数。如果你检查find_if
,天真的假设是你要找的东西应该与容器中存储的类型相同,或者至少可以转换:但std::find_if
算法只要求它们在传递的仿函数中可以比较。
这允许您编写多类型感知仿函数并将与该类型无关的目标对象传递给容器上的类型,并且工作正常。大多数人都不需要这个,他们的代码可以在没有它的情况下找到 - 这也很好。
天真std::find_if
将提取容器的基础类型,比较函数将在该类型的对之间 - 或者,它将是容器类型与事物类型之间的4路比较正在寻找。在一个案例中,我们失去了灵活性 - 在另一种情况下,每个人都支付一个奇怪的角落案件。在C ++中,您只需在需要时支付所需的功能!
std::function
不完美的第四个原因是它基本上是类型擦除的工具。这些是实现细节,但我不知道编译器远离它们。 std::function
的目的是公开单个签名和返回值,并说出&#34;我可以存储与此签名和此返回值匹配的任何内容,您可以将其称为&#34;。它公开了一个静态运行时接口和实现来完成这项任务。初始化std::function
时,它会编译时生成一个帮助对象,该对象将统一std::function
接口中的特定对象包装起来,然后以pImpl
模式存储它。如果你不需要类型擦除,所有这些都是不必要的工作。
标准算法是编写高级代码,几乎与手工制作的解决方案一样高效。即使解决大部分问题也不需要指针函数调用的成本,通过类型擦除std::function
进行虚拟调用。
}; // enum
std::function
是一个非常棒的回调工具,可以替换样板单用途virtual
接口,当你需要隐藏调用者的实现细节时(比如,你需要跨越编译单元边界)用于设计决策)。
好消息是这个问题的更好解决方案正在酝酿之中。特别是,C ++ 14或C ++ 17的目标之一是它将具有某种&#34;概念&#34;支持,您可以说&#34;此模板参数具有以下属性&#34;。确切的语法是不确定的 - C ++ 11概念提案可能会走得很远 - 但是对它有很多热情,现在有一个关于这个问题的工作组。
当它完成时,您将能够使用有意义的概念信息来标记仿函数,该概念说明&#34;这个参数不仅仅是任何类型,而是一种类型的仿函数它接受两个值(包括包含的数据类型),并返回bool
兼容值&#34;您无需转到该函数的文档即可了解编译器,您的IDE以及。