C ++函数作为Template Argument vs Parameter传递

时间:2013-05-08 11:55:07

标签: c++ function templates

在C ++中,有两种方法可以将函数传递给另一个看似等效的函数。

#include <iostream>

int add1(int i){ return i+1; }
int add2(int i){ return i+2; }

template <int (*T)(int) >
void doTemplate(int i){
    std::cout << "Do Template: " << T(i) << "\n";
}

void doParam(int i, int (*f)(int)){
    std::cout << "Do Param: " << f(i) << "\n";
}

int main(){
    doTemplate<add1>(0);
    doTemplate<add2>(0);

    doParam(0, add1);
    doParam(0, add2);
}

doTemplate接受一个函数作为模板参数,而doParam接受它作为一个函数指针,它们似乎都给出相同的结果。

使用每种方法之间有什么权衡?

2 个答案:

答案 0 :(得分:10)

基于模板的版本允许编译器内联调用,因为函数的地址在编译时是已知的。显然,缺点是函数的地址在编译时是已知的(因为你将它用作模板参数),有时这可能是不可能的。

这使我们进入第二种情况,其中函数指针只能在运行时确定,因此编译器无法执行内联,但是可以灵活地在运行时确定函数被称为:

bool runtimeBooleanExpr = /* ... */;
doParam(0, runtimeBooleanExpr ? add1 : add2);

但请注意,还有第三种方法:

template<typename F>
void doParam(int i, F f){
    std::cout << "Do Param: " << f(i) << "\n";
}

这为您提供了更大的灵活性,并且仍然具有在编译时了解将要调用的函数的优势:

doParam(0, add1);
doParam(0, add2);

它还允许传递任何可调用对象而不是函数指针:

doParam(0, my_functor());

int fortyTwo = 42;
doParam(0, [=] (int i) { return i + fortyTwo; /* or whatever... */ }

为了完整性,还有一种第四种方式,使用std::function

void doParam(int x, std::function<int(int)> f);

具有相同的通用性(因为你可以传递任何可调用对象),但也允许你在运行时确定可调用对象 - 最有可能带来性能损失,因为(再一次)内联变得不可能对于编译器。

有关最后两个选项的进一步讨论,请参阅this Q&A on StackOverflow

答案 1 :(得分:5)

模板参数

  1. 必须在编译时
  2. 知道
  3. 为参数的每个不同值导致一个函数即时(所谓的模板膨胀
  4. 被调用的函数对编译器是透明的(内联,但可能会导致更加臃肿,双刃剑)
  5. 对于参数的特定值,调用函数可以重载,而无需修改现有代码
  6. 功能指针

    1. 运行时传递。
    2. 只会导致一个调用函数(较小的对象代码
    3. 被调用函数通常对编译器不敏感(无内联
    4. 调用函数需要运行时if / switch来为参数的特殊值执行特殊操作,这是脆弱
    5. 何时使用哪个版本:如果您需要速度和大量自定义,请使用模板。如果您在运行时需要灵活性,而不是在实现中,请使用函数指针。

      正如@AndyProwl所指出的:如果你有一个C ++ 11编译器,函数指针将被推广到可调用的对象,如std::function和lambda表达式。这开启了一整套新的蠕虫(在很好的意义上)。