C++ std::函数与模板

C++ std::函数与模板,c++,templates,c++11,std-function,C++,Templates,C++11,Std Function,多亏了C++11,我们收到了函子包装器的std::function系列。不幸的是,我一直只听到关于这些新添加的不好的事情。最受欢迎的是它们的速度非常慢。我对它进行了测试,与模板相比,它们真的很糟糕 #include <iostream> #include <functional> #include <string> #include <chrono> template <typename F> float calc1(F f) { r

多亏了C++11,我们收到了函子包装器的
std::function
系列。不幸的是,我一直只听到关于这些新添加的不好的事情。最受欢迎的是它们的速度非常慢。我对它进行了测试,与模板相比,它们真的很糟糕

#include <iostream>
#include <functional>
#include <string>
#include <chrono>

template <typename F>
float calc1(F f) { return -1.0f * f(3.3f) + 666.0f; }

float calc2(std::function<float(float)> f) { return -1.0f * f(3.3f) + 666.0f; }

int main() {
    using namespace std::chrono;

    const auto tp1 = system_clock::now();
    for (int i = 0; i < 1e8; ++i) {
        calc1([](float arg){ return arg * 0.5f; });
    }
    const auto tp2 = high_resolution_clock::now();

    const auto d = duration_cast<milliseconds>(tp2 - tp1);  
    std::cout << d.count() << std::endl;
    return 0;
}
#包括
#包括
#包括
#包括
模板
浮点计算C1(F){返回-1.0f*F(3.3f)+666.0f;}
float calc2(std::function f){return-1.0f*f(3.3f)+666.0f;}
int main(){
使用名称空间std::chrono;
const auto tp1=系统时钟::现在();
对于(int i=0;i<1e8;++i){
calc1([](浮点arg){返回arg*0.5f;});
}
const auto tp2=高分辨率时钟::now();
const auto d=持续时间(tp2-tp1);

std::cout不同的是不一样的

它的速度较慢,因为它执行模板无法执行的操作。特别是,它允许您调用任何可以使用给定参数类型调用且其返回类型可以从同一代码转换为给定返回类型的函数

void eval(const std::function<int(int)>& f) {
    std::cout << f(3);
}

int f1(int i) {
    return i;
}

float f2(double d) {
    return d;
}

int main() {
    std::function<int(int)> fun(f1);
    eval(fun);
    fun = f2;
    eval(fun);
    return 0;
}
void eval(const std::function&f){

std::cout一般来说,如果你面临一个让你有选择的设计环境,使用模板。我强调了“设计”一词,因为我认为你需要关注的是
std::function
的用例和模板之间的区别,它们是非常不同的

一般来说,模板的选择只是一个更广泛原则的实例:尝试在编译时指定尽可能多的约束。理由很简单:即使在生成程序之前,如果您能够捕获错误或类型不匹配,您也不会向客户发送有缺陷的程序

此外,正如您正确指出的那样,对模板函数的调用是静态解析的(即在编译时),因此编译器拥有优化和可能内联代码所需的所有信息(如果调用是通过vtable执行的,则这是不可能的)

是的,模板支持确实不是完美的,C++11仍然缺乏对概念的支持;但是,我不知道
std::function
在这方面能为您节省多少钱。
std::function
不是模板的替代品,而是无法使用模板的设计情况下的工具

当您需要在运行时通过调用遵循特定签名但其具体类型在编译时未知的可调用对象来解析调用时,就会出现这样一种用例;注册回调的类型和数量在运行时根据程序的状态和应用程序逻辑确定。其中一些回调可能是函子,一些可能是普通函数,一些可能是将其他函数绑定到某些参数的结果

<>代码> STD::函数< /COD>和 STD::BIN < /COD>还提供了一个自然的习语,用于C++中的函数编程,其中函数被当作对象,并自然地被处理并组合成其他函数。尽管这种组合也可以用模板实现,但类似的设计情况通常会出现。以及需要在运行时确定组合可调用对象类型的用例

最后,还有一些情况下,
std::function
是不可避免的,例如,如果您想编写;然而,这些限制更多地是由技术限制而不是我认为的概念差异决定的


总而言之,关注设计并尝试了解这两种结构的概念用例。如果你用你的方式将它们进行比较,你将迫使它们进入一个它们可能不属于的领域。

你已经有了一些很好的答案,所以我不会反驳它们,简而言之,比较std::函数与模板类似于将虚拟函数与函数进行比较。
您永远不应该“偏爱”虚拟函数而不是函数,而是在适合问题时使用虚拟函数,将决策从编译时转移到运行时。其思想是不必使用定制的解决方案(如跳转表)来解决问题您使用的东西可以为编译器提供更好的优化机会。如果您使用标准解决方案,它也可以帮助其他程序员。

Andy Prowl很好地讨论了设计问题。这当然非常重要,但我相信最初的问题涉及到与
std::function
相关的更多性能问题

首先,快速评论一下测量技术:
calc1
获得的11ms毫无意义。事实上,查看生成的程序集(或调试程序集代码),可以看出VS2012的优化器足够聪明,能够意识到调用
calc1
的结果独立于迭代,并将调用移出循环:

for (int i = 0; i < 1e8; ++i) {
}
calc1([](float arg){ return arg * 0.5f; });
对于这个版本,时间大约为16000毫秒(相比之下,原始代码为1241毫秒)

最后,请注意lambda的生存期包含
std::function
的生存期。在这种情况下,
std::function
可以存储对lambda的“引用”,而不是存储lambda的副本我指的是通过函数
std::ref
std::cref
轻松构建的
std::ref\u包装

auto func = [a,b,c](float arg){ return arg * 0.5f; };
calc2(std::cref(func));
时间减少到大约1860毫秒

我不久前写过:

正如我在文章中所说的,由于VS2010对C++11的支持较差,这些论点不太适用于VS2010。在撰写本文时,只有VS2012的测试版可用,但它对C++11的支持已经足够好了。

对于Clang,没有性能
auto func = [a,b,c](float arg){ return arg * 0.5f; };
calc2(std::cref(func));
float result=0;
for (int i = 0; i < 1e8; ++i) {
  result+=calc2([](float arg){ return arg * 0.5f; });
}
1.71799e+10, time spent 0.14 sec
6.6435e+10, time spent 5.772 sec
clang:        calc1:           1.4 seconds
clang:        calc2:           1.4 seconds (identical binary)

gcc 4.7.2:    calc1:           1.1 seconds
gcc 4.7.2:    calc2:           6.0 seconds

VS2012 CTPNov calc1:           0.8 seconds 
VS2012 CTPNov calc2:           2.0 seconds 

VS2015 (14.0.23.107) calc1:    1.1 seconds 
VS2015 (14.0.23.107) calc2:    1.5 seconds 

MinGW (4.7.2) calc1:           0.9 seconds
MinGW (4.7.2) calc2:          20.5 seconds 
#include <functional>
#include <chrono>
#include <iostream>

template <typename F>
float calc1(F f, float x) { 
  return 1.0f + 0.002*x+f(x*1.223) ; 
}

float calc2(std::function<float(float)> f,float x) { 
  return 1.0f + 0.002*x+f(x*1.223) ; 
}

int main() {
    using namespace std::chrono;

    const auto tp1 = high_resolution_clock::now();

    float result=0;
    for (int i = 0; i < 1e8; ++i) {
      result=calc1([](float arg){ 
          return arg * 0.5f; 
        },result);
    }
    const auto tp2 = high_resolution_clock::now();

    const auto d = duration_cast<milliseconds>(tp2 - tp1);  
    std::cout << d.count() << std::endl;
    std::cout << result<< std::endl;
    return 0;
}
#include <functional>


// simple funct
float func_half( float x ) { return x * 0.5; }

// func we can bind
float mul_by( float x, float scale ) { return x * scale; }

//
// func to call another func a zillion times.
//
float test_stdfunc( std::function<float(float)> const & func, int nloops ) {
    float x = 1.0;
    float y = 0.0;
    for(int i =0; i < nloops; i++ ){
        y += x;
        x = func(x);
    }
    return y;
}

// same thing with a function pointer
float test_funcptr( float (*func)(float), int nloops ) {
    float x = 1.0;
    float y = 0.0;
    for(int i =0; i < nloops; i++ ){
        y += x;
        x = func(x);
    }
    return y;
}

// same thing with inline function
float test_inline(  int nloops ) {
    float x = 1.0;
    float y = 0.0;
    for(int i =0; i < nloops; i++ ){
        y += x;
        x = func_half(x);
    }
    return y;
}
#include <iostream>
#include <functional>
#include <chrono>

extern float func_half( float x );
extern float mul_by( float x, float scale );
extern float test_inline(  int nloops );
extern float test_stdfunc( std::function<float(float)> const & func, int nloops );
extern float test_funcptr( float (*func)(float), int nloops );

int main() {
    using namespace std::chrono;


    for(int icase = 0; icase < 4; icase ++ ){
        const auto tp1 = system_clock::now();

        float result;
        switch( icase ){
         case 0:
            result = test_inline( 1e9);
            break;
         case 1:
            result = test_funcptr( func_half, 1e9);
            break;
         case 2:
            result = test_stdfunc( func_half, 1e9);
            break;
         case 3:
            result = test_stdfunc( std::bind( mul_by, std::placeholders::_1, 0.5), 1e9);
            break;
        }
        const auto tp2 = high_resolution_clock::now();

        const auto d = duration_cast<milliseconds>(tp2 - tp1);  
        std::cout << d.count() << std::endl;
        std::cout << result<< std::endl;
    }
    return 0;
}
movq    (%rdi), %rax                ; get the std::func data
movsd   8(%rax), %xmm1              ; get the bound value (0.5)
movq    (%rax), %rdx                ; get the function to call (mul_by)
cvtpd2ps    %xmm1, %xmm1        ; convert 0.5 to 0.5f
jmp *%rdx                       ; jump to the func
movl    $16, %edi
movq    $0, 32(%rsp)
call    operator new(unsigned long)      ; get 16 bytes for std::function
movsd   .LC0(%rip), %xmm1                ; get 0.5
leaq    16(%rsp), %rdi                   ; (1st parm to test_stdfunc) 
movq    mul_by(float, float), (%rax)     ; store &mul_by  in std::function
movl    $1000000000, %esi                ; (2nd parm to test_stdfunc)
movsd   %xmm1, 8(%rax)                   ; store 0.5 in std::function
movq    %rax, 16(%rsp)                   ; save ptr to allocated mem

   ;; the next two ops store pointers to generated code related to the std::function.
   ;; the first one points to the adaptor I showed above.

movq    std::_Function_handler<float (float), std::_Bind<float (*(std::_Placeholder<1>, double))(float, float)> >::_M_invoke(std::_Any_data const&, float), 40(%rsp)
movq    std::_Function_base::_Base_manager<std::_Bind<float (*(std::_Placeholder<1>, double))(float, float)> >::_M_manager(std::_Any_data&, std::_Any_data const&, std::_Manager_operation), 32(%rsp)


call    test_stdfunc(std::function<float (float)> const&, int)
template <typename F>
float calc1(F f, float i) { return -1.0f * f(i) + 666.0f; }
float calc2(std::function<float(float)> f, float i) { return -1.0f * f(i) + 666.0f; }
int main() {
    const auto tp1 = system_clock::now();
    for (int i = 0; i < 1e8; ++i) {
        t += calc2([&](float arg){ return arg * 0.5f + t; }, i);
    }
    const auto tp2 = high_resolution_clock::now();
}
.L34:
cvtsi2ss        %edx, %xmm0
addl    $1, %edx
movaps  %xmm3, %xmm5
mulss   %xmm4, %xmm0
addss   %xmm1, %xmm0
subss   %xmm0, %xmm5
movaps  %xmm5, %xmm0
addss   %xmm1, %xmm0
cvtsi2sd        %edx, %xmm1
ucomisd %xmm1, %xmm2
ja      .L37
movss   %xmm0, 16(%rsp)
float calc3(float i) {  return -1.0f * f2(i) + 666.0f; }
std::function<float(float)> f2 = [](float arg){ return arg * 0.5f; };

int main() {
    const auto tp1 = system_clock::now();
    for (int i = 0; i < 1e8; ++i) {
        t += calc3([&](float arg){ return arg * 0.5f + t; }, i);
    }
    const auto tp2 = high_resolution_clock::now();
}
template<class Func, typename Ret, typename... Args>
concept functor = std::regular_invocable<Func, Args...> && 
                  std::same_as<std::invoke_result_t<Func, Args...>, Ret>;
template <functor<double,int> F>
auto CalculateSomething(F&& f, int const arg) {
  return f(arg)*f(arg);
}
template <typename... Args, functor<double, Args...> F>
auto CalculateSomething(F&& f, Args... args) {
  return f(args...)*f(args...);
}