std::tr1::function and std::tr1::bind

4

我在使用一个非常复杂的C函数时遇到了问题,这个函数是在一个C++类中使用的(重写C函数不是一种选择)。C函数:

typedef void (*integrand) (unsigned ndim, const double* x, void* fdata,
                           unsigned fdim, double* fval);
// This one:
int adapt_integrate(unsigned fdim, integrand f, void* fdata,
                    unsigned dim, const double* xmin, const double* xmax, 
                    unsigned maxEval, double reqAbsError, double reqRelError, 
                            double* val, double* err);

我需要提供一个返回类型为integrand的空函数,然后adapt_integrate将计算n维积分。如果func是一个独立的函数,下面的calcTripleIntegral代码可以作为独立的函数工作。

我想传递一个(非静态!)类成员函数作为被积函数,因为这样可以轻松地进行重载等操作...

class myIntegrator
{
public:
    double calcTripleIntegral( double x, double Q2, std::tr1::function<integrand> &func ) const
    {
        //...declare val, err, xMin, xMax and input(x,Q2) ...//
        adapt_integrate( 1, func, input,
                         3, xMin, xMax,
                         0, 0, 1e-4,
                         &val, &err);
        return val;
    }
    double integrandF2( unsigned ndim, const double *x, void *, // no matter what's inside
                 unsigned fdim, double *fval) const;            // this qualifies as an integrand if it were not a class member
    double getValue( double x, double Q2 ) const
    {
        std::tr1::function<integrand> func(std::tr1::bind(&myIntegrator::integrandF2, *this);
        return calcTripleIntegral(x,Q2,func);
    }
}

在GCC 4.4.5(预发布版)上,这给了我以下错误:

error: variable 'std::tr1::function func' has initializer but incomplete type

编辑:我的代码有什么错误?我现在已经尝试使用GCC 4.4、4.5和4.6编译,结果都是相同的错误。要么没有对此进行任何处理,要么是我做错了/编辑

非常感谢!如果我表达不清楚,我很乐意解释。

附注:我能否通过在myIntegrator.cpp中使用指向某个函数的函数指针来解决这个问题而不使用tr1的东西?

最终更新:好吧,我错误地认为TR1提供了一个一两行的解决方案。很遗憾。我正在将我的类转换为命名空间并复制粘贴函数声明。我只需要一个基类和一个重新实现接口的子类。C函数指针+C++类=对我来说不好的消息。 无论如何,感谢所有的答案,你们向我展示了C++的一些黑暗角落;)


fdatainput参数是关于什么的?为什么积分类型中的void*参数没有名称? - deft_code
@A.Le:Caspin知道这一点。问题是我们不知道void* fdata是否被传递 - 如果integrandvoid*参数命名得当,我们就不必猜测/询问了。 - Georg Fritzsche
啊!我真傻!我把一个引导性问题误认为是真正的无知。对不起,Caspin! - A. Levy
我知道当我问这个问题时,有人会向我解释为什么它们不需要。也许我应该在我的问题前加上“我知道...”。 - deft_code
我添加了void* name,这归结为integrand中的fdata参数,即涉及数学函数的用户提供的参数。 - rubenvb
显示剩余3条评论
7个回答

3

您有三个问题... 首先,您想要一个 std::tr1::function<R (Args..)>,但是你的实际需要的是 std::tr1::function<R (*)(Args...)> - 因此您需要两个typedef:

typedef void (integrand) (unsigned ndim, const double *x, void *,
                       unsigned fdim, double *fval);
typedef integrand* integrand_ptr;

...因此,第一个允许您编译的function<integrand>。必须相应地修复adapt_integrate

int adapt_integrate(unsigned fdim, integrand_ptr f, ...);

接下来你的bind语法有误,应该是:

std::tr1::bind(&myIntegrator::integrandF2, *this, _1, _2, _3, _4, _5);

剩下的问题是tr1::function<T>无法转换为函数指针,因此您需要通过包装函数来使用void* fdata参数传递上下文。例如:

extern "C" void integrand_helper (unsigned ndim, const double *x, void* data,
                                  unsigned fdim, double *fval)
{
    typedef std::tr1::function<integrand> Functor;
    Functor& f = *static_cast<Functor*>(data);
    f(ndim, x, data, fdim, fval);
}

// ...
adapt_integrate(1, &integrand_helper, &func, ...);

这当然是假设void*参数被传递到函数中,否则就会变得很丑陋。
另一方面,如果void* fdata允许传递上下文,那么所有的tr1::function内容都是不必要的,你可以直接通过跳板函数进行通信,只需将this作为上下文参数传递即可。
extern "C" void integrand_helper (unsigned ndim, const double *x, void* data,
                                  unsigned fdim, double *fval)
{
    static_cast<myIntegrator*>(data)->integrandF2(ndim, ...);
}

// ...
adapt_integrate(1, &integrand_helper, this, ...);

3

如果您只是想将成员函数传递到C风格的回调中,可以不使用std :: t1 :: bindstd :: tr1 :: function

class myIntegrator
{
public:
   // getValue is no longer const.  but integrandF2 wasn't changed
   double getValue( double x, double Q2 )
   {
      m_x = x;
      m_Q2 = Q2;

      // these could be members if they need to change
      const double xMin[3] = {0.0};
      const double xMax[3] = {1.0,1.0,1.0};
      const unsigned maxEval = 0;
      double reqAbsError = 0.0;
      double reqRelError = 1e-4;

      double val;

      adapt_integrate( 1, &myIntegrator::fancy_integrand,
                       reinterpret_cast<void*>(this),
                       3, xMin, xMax,
                       maxEval, reqAbsError, reqRelError,
                       &val, &m_err);

      return val;
   }

   double get_error()
   { return m_error; }

private:
   // use m_x and m_Q2 internally
   // I removed the unused void* parameter
   double integrandF2( unsigned ndim, const double *x,
                       unsigned fdim, double *fval) const;

   static double fancy_integrand( unsigned ndim, const double* x, void* this_ptr,
                                  unsigned fdim, double* fval)
   {
      myIntegrator& self = reinterpret_cast<myIntegrator*>(this_ptr);
      self.integrateF2(ndim,x,fdim,fval);
   }

   double m_x
   double m_Q2;
   double m_err;
};

这不是欺骗编译器从静态函数访问非静态成员吗?这样做很危险,可能会导致崩溃。 - rubenvb
@rubenvb:不,这是完全安全、可移植和良好的C++风格。我只是使用静态函数来访问myIntegrator的内部。如果我希望,fancy_integrand可以成为一个独立的函数,并且所有myIntegrator的成员都可以被公开。我喜欢静态方法的方式,因为它更加封装。 - deft_code
虽然标题可能不容易回答,需要绑定和/或函数,但它确实解决了手头的问题。感谢您的耐心等待 :) - rubenvb

2

我想传递一个(非静态的!)类成员函数作为被积函数...

你做不到。如果你在SO上搜索使用成员函数作为回调函数,你一定会找到有用的信息,包括你正在尝试的直接方法是不可能的事实。

编辑:顺便说一句,你代码中的一个问题(当然还有更多,因为你正在尝试的东西根本不可能)是你向function<>传递了一个函数指针类型,而它期望的是一个签名。这个函数模板的实现大致如下:

template < typename Signature >
struct function;

// for each possible number of arguments:
template < typename R, typename Arg1, typename Arg2 >
struct function<R(Arg1,Arg2)>
{
   ... body ...
};

如您所见,将函数指针传递给这种类型的东西是不可能被编译器理解的。它会尝试实例化前向声明但却无法成功。当然,这就是您收到的编译器错误的原因,但它并没有解决您的根本问题,即您正在做的事情永远不会起作用。
在完全的C++0x编译器中,可以以不同的方式完成此操作,但boost::function和MSVC版本必须像这样。此外,C++0x版本将面临您目前面临的相同问题。

这正是我使用functionbind来解决这个问题的原因,例如请参考https://dev59.com/Z3E95IYBdhLWcg3wSb_P(第二个答案是我的灵感来源)。 - rubenvb
@rub:第二个答案不必通过C函数/作为普通函数指针传递-这就是问题所在。 - Georg Fritzsche
这个回答是正确的。我在看void参数-那是一个类似于userdata的参数吗?许多C回调为任何你需要的上下文提供了void。如果你的C函数指针没有多余的void*,那么你就做不到这一点。 - Puppy

2

由于std::tr1::bind和c-style函数指针不兼容,因此请尝试使用以下方法。它会起作用,只是myIntegrator::getValue不再是线程安全的。如果从接口中删除calcTripleIntegral,那么这将变得更简单,并且不需要使用std::tr1::bindstd::tr1::function

class myIntegrator
{
public:
   double getValue( double x, double Q2 ) const
   {
       return calcTripleIntegral(x,Q2,std::tr1::bind(&Integrator::integrandF2,this));
   }

   double calcTripleIntegral( double x, double Q2, const std::tr1::function<integrand>& func ) const
   {
      assert( s_integrator == NULL );
      s_integrator = this;
      m_integrand = func;

      //...declare val, err, xMin, xMax and input(x,Q2) ...//
      adapt_integrate( 1, &myIntegrator::fancy_integrand, input,
                       3, xMin, xMax,
                       0, 0, 1e-4,
                       &val, &err);

      assert( s_integrator == this);
      s_integrator = NULL;

      return val;
   }
private:
   double integrandF2( unsigned ndim, const double *x, void *,
                unsigned fdim, double *fval) const;

   static double fancy_integrand( unsigned ndim, const double* x, void* input,
                                  unsigned fdim, double* fval)
   {
      s_integrator->integrateF2(ndim,x,input,fdim,fval);
   }

   std::tr1::function<integrand> m_integrand;
   static const myIntegrator* s_integrator;
};

看起来像是我想的,不过如果那个 void* 参数没有通过的话,我会感到惊讶的 :) - Georg Fritzsche
如果删除一个像calcTripleIntegral这样的便利函数(它只是定义/声明了val、err、input、xMin和xMax参数),会使std::tr1::bind和function变得多余吗?我可以将其删除,但不知道它如何简化事情。我从未使用过这个tr1功能,也没有找到一个好的(非boost)解释 :( - rubenvb
如果我尝试这样做,会出现关于m_integrand类型不完整的错误 :( - rubenvb
正如我和其他人指出的那样,function不接受函数指针类型——function<void (*)(int)>,而是接受函数类型——function<void (int)>。使用前者会导致你遇到这个错误。 - Georg Fritzsche
@rubenvb:从接口中删除caldTripleIntegral,也会将std::tr1::bindstd::tr1::function从接口中删除。getValue并没有说明它是如何实现的,两个double输入一个double输出。在内部,我可以(间接地)将成员函数传递给updapt_integrate而不使用std::tr1::bind - deft_code
@caspin: 这样做是行不通的,因为adapt_integrate期望的是一个普通的独立函数,而不是一个带有隐式this引用的成员函数。这就是我的问题所在。只有静态函数可以实现这一点,而静态函数无法访问非静态成员变量,而我需要做到这一点 :( - rubenvb

1
假设C-API允许传递类型不可知的(在这种情况下,C-API函数不必知道其类型,而是依赖于回调函数来知道它需要什么)上下文参数(在回调函数中通常是这种情况;在这种情况下,我怀疑fdata参数是类似这样的东西),则将函数对象作为此上下文参数的一部分传递。
然后它应该看起来像这样:
#include <iostream>
#include <tr1/functional>

typedef void (*callback_function_t)(void *input, int arg);

struct data_type { 
  int x;
};

struct context_type {
  std::tr1::function<void(data_type const &, int)> func;
  data_type data;
};

void callback(data_type const&data, int x) {
  std::cout << data.x << ", " << x << std::endl;
}

void callback_relay(void *context, int x) {
  context_type const *ctxt = reinterpret_cast<context_type const*>(context);
  ctxt->func(ctxt->data, x);
}

void call_callback(callback_function_t func, void *context, int x) {
  func(context, x);
}

int main() {
  context_type ctxt = { callback, { 1 } };

  call_callback(callback_relay, &ctxt, 2);
}

其中call_callback是C-API函数。这样,您可以将支持函数调用语法的任何内容分配给context_type :: func,包括std :: tr1 :: bind表达式。此外,尽管(我感到有道德义务提到这一点),严格来说,并未定义C和C ++函数的调用约定相同,但在实践中,您可以使context_type成为类模板,callback_relay成为函数模板,以使context_type :: data更加灵活,并通过这种方式传递任何您喜欢的内容。


0

bind 的工作方式与您所想的有些不同。您需要为每个参数提供一个值或占位符。

对于您的示例,这可以简化为(使用占位符)

std::tr1::function<integrand> func(std::tr1::bind(&myIntegrator::integrandF2, *this, _1, _2, _3, _4, _5));

由于您绑定了一个成员函数,因此您会得到一个额外的(隐式)参数,即调用该成员函数的对象,因此您有六个参数。

对于第一个参数,您绑定了this对象,对于其他参数,您只需传递占位符。

顺便说一句,您的成员函数返回double,而函数声明返回void。

(记录一下,我仍在使用较旧的编译器,支持tr1很少,因此我只有使用boost时的bindfunction经验,也许tr1的情况有所改变...)


0
那个错误信息听起来像是你漏掉了某一个类型的 include。至少请再次确认一下你的 integrand 和 tr1 的 includes 是否正确?

我已经包含了<tr/functional>和<tr1/memory>,还能包含什么?integrand在Cubature.h中定义,我已经包含了它。这段代码以独立的无类形式工作正常,所以integrand不是问题所在。 - rubenvb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接