的std ::函数,而不是谓词模板(std::function instead of templat

2019-07-21 14:24发布

许多标准库算法取谓词的功能。 然而,这些谓词的类型是任意的,用户提供的模板的参数。 为什么不C ++ 11规定,这些都需要一个特定的类型,比如std::function呢? 例如:

template< class InputIt >
InputIt find_if( InputIt first, InputIt last,
             std::function<bool()> p );

是不是用这个代替模板作为参数类型不是更干净?

Answer 1:

std::function运行时多态性。 有什么特别std::function实例可以存储任何类型(A型这是适合的函子std::function的签名,当然)。

标准库算法和如模板它们的功能参数的类型。 因此,他们并不需要运行时多态性做好自己的工作; 他们依靠编译时多态性。

最重要的是,这样的算法并不需要强制你运行时多态性的成本 。 如果你想运行时多态性,你可以给它std::function或什么的。 如果你想编译时多态,你提供给它一个类型不使用多态分派(又名:大部分仿函数或函数)。

运行时多态性的成本还包括无法内联函数调用。 使用合适的仿函数(或偶函数指针,这取决于你的编译器有多好),编译器通常可以内联函数调用,如果它希望这样。 随着运行时多态性,不仅是你付出的运行调度的成本(可能包括额外的参数转发成本),你也失去重要的优化机会。



Answer 2:

性能!

模板库功能是非常非常不是更好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;
    }
}

模板模式:2.13861e-006

的std ::功能模式:0.220006

Windows7的上GCC 4.7.2 -O2 Core2双核CPU主频为2.40GHz



Answer 3:

因为std::function不完善。

它是如何完美? 让我列举的方式。

{

首先, std::function不支持将传入任意对象的完美fowarding。 而且,在实践中,它不能。 std::function公开了一个固定的签名来电,并能接受多种被叫方的,完善的转发需要每个主叫方和被叫者量身定做的签名。 它支持的正是在其签名的公开争论完美的转发,但这是不够的。

想象一下,一个std::function有两个参数,并intdouble 。 为它做完美转发,就必须接受int&int&&int const& ,同组次double (更不用说挥发性一些变体物)。 每个签名数量std::function必须接受决绝完美转发与它有参数的数量呈指数级增长。 std::function的一套它暴露(目前1)被固定在实例化,而将其设置暴露签名的模板是无限的,仅用于当生成的签名的。 因为有些功能状物体做优化这些情况有所不同这一点很重要! 所以每次std::function已取出的机会,完全向前调用包装类型。

第二个原因std::function不完善是因为编译器吸。 如果包装在一个lambda std::function然后调用它的算法,在理论上,编译器可以实现这个std::function是包装一些固定的λ -但在实践中,它失去了这一事实的轨道,和对待该std::function为周围的一些通用类的包装。 因此,即使在签名案件std::function完全匹配算法的使用情况,防止类型瓶颈std::function作出转发不完善,在实践中会有开销由于类型-擦除所进行std::function ,编译器会觉得很难优化过std::function调用“屏障”。

第三个原因std::function不完善的是,它会鼓励算法作家过分限制哪些参数可以在算法进行传递。 如果您检查find_if ,天真的假设是,你正在寻找的东西应该是相同类型储存在容器中的类型,或者至少可转换:但std::find_if算法只要求它们在传递comparible在仿函数。

这允许你写多类型的感知函子,并通过在目标对象,它是一个不相关类型的容器的类型,以及工作的事情就好了。 大多数人并不需要这一点,他们的代码的作品没有找到它 - 这是很好的为好。

幼稚std::find_if将提取基础类型的容器的,并比较功能将是对这种类型的间-或,这将是被搜索的容器类型和事物的类型之间的4路比较。 在一个案例中,我们失去弹性 - 在另一种情况下,每个人都付出了一个奇怪的极端情况。 而在C ++中,你应该只支付当你需要他们,你需要的功能!

A中提出理由std::function不完善的是,它基本上是类型擦除工具。 这些都是实现细节,但我不知道该从他们迷路远编译器的。 一个std::function的目的是揭露单一的签名和返回值,并说:‘我可以存储这个签名,这个返回值匹配任何东西,你可以把它称为’。 它公开了一个静态的运行时间接口和实现做这个任务。 当你初始化std::function ,它编译时工作,产生包装辅助对象,在统一的特定对象std::function接口,然后存储,在pImpl模式。 所有这些是不必要的,如果你不需要类型擦除工作。

标准算法是如何编写高水平的代码,几乎与手工制作的解决方案,高效。 即使不需要指针函数调用的成本来解决这些问题的大部分,沿虚拟呼叫让通过清除类型std::function

}; // enum

std::function是回调一个真棒工具,以取代样板单一用途virtual接口,当你需要隐藏主叫方的实现细节(比如,你需要跨越的设计决策编译单元边界)。

好消息是,这个问题更好的解决方案被灌进管道。 特别是,对C ++ 14或C ++ 17的目标之一是,它要具有某种“概念”的支持,在这里你可以说“这个模板参数具有以下特性”。 什么确切的语法会远一些 - 在C ++ 11概念提案很可能将路远 - 但有很多关于它的积极性,并有现在对这个问题的工作组。

当或者如果它这样做,你就可以庆祝了有意义的概念信息的函子,说:“这种说法是不是任何类型,而是一种类型,它是取两个值(包括所包含的数据类型算符),并返回一个bool兼容值”的编译器,你的IDE,你可以不必再为函数的文档了解。



文章来源: std::function instead of templates for predicates