【问题标题】:C++ Function passed as Template Argument vs ParameterC++ 函数作为模板参数与参数传递
【发布时间】:2023-03-26 14:03:01
【问题描述】:

在 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 接受它作为一个函数指针,它们似乎都给出了相同的结果。

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

【问题讨论】:

  • 试试doParam(0, some_condition ? add1 : add2)看看有什么不同。

标签: c++ function templates


【解决方案1】:

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

这将我们带到第二种情况,其中函数指针可能仅在运行时确定,因此编译器无法执行内联,但让您可以灵活地在运行时确定要执行的函数被称为:

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

【讨论】:

  • 你能解释一下lambda中的=吗?不过我还在揉眼睛:-)
  • @Koushik:它按值捕获fortyTwo 变量。非捕获 lambda 不是一个很好的例子,因为它们可以隐式转换为函数指针;)
  • [=] 使其默认按值(而不是引用)捕获值。所以它捕获了 fortyTwo 的值 42 而不是对它的引用。这意味着如果您之后为其分配 111,则 lambda 仍将使用它捕获的值 42。
  • @Koushik:你的意思是,作为一种捕捉方式?不,不是在 C++11 中。如果你的意思是作为 lambda 的参数 ([] (int const&amp;) { ... }),是的,你可以,但是对于 int,我不会费心参考 const
  • @AndyProwl 我的意思是捕捉。现在学会了谢谢:-) 也感谢 jcoder:-)
【解决方案2】:

模板参数

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

函数指针

  1. 运行时传递。
  2. 只导致一个调用函数(更小的目标代码
  3. 调用的函数通常对编译器不透明(无内联
  4. 调用函数需要一个运行时if/switch 来为参数的特殊值做一些特殊的事情,这是脆弱的

何时使用哪个版本:如果您需要速度和大量自定义,请使用模板。如果您在运行时需要灵活性,但在实现中不需要,请使用函数指针。

正如@AndyProwl 指出的那样:如果你有一个 C++11 编译器,函数指针会泛化为可调用对象,例如 std::function 和 lambda 表达式。这打开了一个全新的蠕虫罐(从某种意义上说)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-04-02
    • 1970-01-01
    • 1970-01-01
    • 2010-11-13
    • 2017-06-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多