【问题标题】:Flexible funtion calling with void* and void**使用 void* 和 void** 进行灵活的函数调用
【发布时间】:2017-01-24 11:25:50
【问题描述】:

我希望编写一个函数,它使用double 函数的返回值,但是/并且我想在函数调用期间选择这个双精度函数。我的想法是:

#include <iostream>

double linea(double a, double b, double x);
double parabola(double a, double b, double c, double x);

void konjgrad(double (*function)(void** params), void** params);

double linea(double a, double b, double x){
    return a*x+b;
}

double parabola(double a, double b, double c, double x){
    return a*x*x+b*x+c;
}

void konjgrad(double (*function)(void** params), void** params){
    double d; 
    d=function(params);
    std::cout<<d<<std::endl;
}

int main(){
    konjgrad(linea(1.6,5.1,2.6));    
    konjgrad(parabola(2.4,3.1,4,2.6));

    return 0;
}

但我陷入了指针的迷宫。有没有人知道如何解决这个问题?

【问题讨论】:

  • 无论如何。可能的解决方案范围从使用模板到通过std::function 进行类型擦除。您可以选择更符合您需求的任何一个。
  • konjgrad(linea(1.6,5.1,2.6)); - 你没有传递指向函数的指针,你只是调用函数并传递它的结果

标签: c++ pointers void-pointers


【解决方案1】:

您可以使用variadic templates 和perfect-forwarding 来引入所需的灵活性,而无需任何额外的运行时开销:

template <typename TF, typename... TArgs>
void konjgrad(TF&& f, TArgs&&... args)
{
    double d;
    d = std::forward<TF>(f)(std::forward<TArgs>(args)...);
    std::cout<<d<<std::endl;
}

konjgrad 可以这样调用:

konjgrad(linea, 1.6, 5.1, 2.6);    
konjgrad(parabola, 2.4, 3.1, 4, 2.6);

wandbox example


或者,您可以使用 lambda expressions 并将绑定参数的负担交给调用者:

template <typename TF>
void konjgrad(TF&& f)
{
    double d;
    d = std::forward<TF>(f)();
    std::cout<<d<<std::endl;
}

konjgrad 可以这样调用:

konjgrad([]{ return linea(1.6, 5.1, 2.6); });    
konjgrad([]{ return parabola(2.4, 3.1, 4, 2.6); });

wandbox example

【讨论】:

  • @xaxxon: 如果f 只有右值调用重载怎么办?
  • 如果您不打算在 lambda 中捕获任何内容,则无需将 konjgrad 模板化:melpon.org/wandbox/permlink/BRZvosFaV9NRYZhI
  • @xaxxon - 将其保留为模板可以提供更多优化空间。当涉及到模板时,可以内联很多内容。
  • @xaxxon:将可调用对象传递给函数的方式有很多权衡。 I wrote an article about different techniques with benchmarks 你可能会感兴趣。
  • @xaxxon:不管std::function的速度如何,它的语义都意味着“所有权”。如果您不需要所有权,请不要使用std::function。此外,“比std::function 更快的实现”无法击败“零开销”:)
【解决方案2】:

已编辑 基于 Vittorio Romeo 的评论,更新为将向量作为 const 引用传递,并在 linea 和 parabola API 中添加了必要的断言。

如果您想避免使用可变参数函数,这里有一些简单的方法。由于您的函数接口已明确定义,您可以在 linea 和 parabola 函数中传入向量或双精度类型参数数组。

#include <vector>

using namespace std;

double linea(const std::vector<double>& params)
{
    assert(params.size() == 3 /*Linear 3 parameters required*/);
    return params[0] * params[1] + params[2];
}

double parabola(const std::vector<double>& params)
{
    assert(params.size() == 4 /*Parabola 4 parameters required*/);
    return params[0] * params[3] * params[3] +
        params[1] * params[3] +
        params[2];
}

konjgrad 函数的小修改版本,它现在通过函数指针从底层调用返回双精度值。请注意,第一个参数明确定义了函数接口。第二个参数是双精度向量。

double konjgrad(double(*function)(const std::vector<double>&), const std::vector<double>& params)
{
    return function(params);//returning value from here
}

最后,您应该如何调用上述定义的 API。

int main(int argc, wchar_t* argv[])
{
    std::vector<double> vals;
    vals.push_back(1.6);
    vals.push_back(5.1);
    vals.push_back(2.6);
    double v1 = konjgrad(linea, vals);

    std::vector<double> parab;
    parab.push_back(2.4);
    parab.push_back(3.1);
    parab.push_back(4.0);
    parab.push_back(2.6);

    double v2 = konjgrad(parabola, parab);

    return 0;

}

希望这会有所帮助。

【讨论】:

  • 我对这个解决方案投了反对票,因为您不必要地分配内存并复制 std::vector 实例。您还假设向量在没有asserting 的情况下具有正确的大小 - 这很危险!另外,调用者的语法一点也不好。
  • 请注意 OP 的第一行“我希望编写一个函数,它使用双精度函数的返回值但是/我想在函数调用期间选择这个双精度函数”,我认为答案是为 OP 的问题提出解决方案。其次,我理解您对向量不作为参考传递(并在堆栈上创建不必要的副本)的担忧,只是想分享这个想法,那么我应该更新代码吗?并添加断言等?
猜你喜欢
  • 2012-03-09
  • 2018-07-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-18
  • 2012-08-16
  • 2012-06-29
  • 1970-01-01
相关资源
最近更新 更多