【问题标题】:std::tr1::function and std::tr1::bindstd::tr1::function 和 std::tr1::bind
【发布时间】:2010-07-15 20:20:55
【问题描述】:

我在 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 类型的 void 函数,adapter_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(预发布)上,这给了我:

错误:变量 'std::tr1::function func' 具有初始化程序但类型不完整

编辑:我的代码有什么错误?我现在尝试使用 GCC 4.4、4.5 和 4.6 进行编译,都导致相同的错误。要么没有做任何工作,要么我做错了什么/EDIT

非常感谢非常!如果我不够清楚,我很乐意详细说明。

PS:我可以通过使用指向 myIntegrator.cpp 中某处定义的函数的函数指针来解决这个问题吗?

最终更新:好的,我误以为 TR1 提供了一个/两行解决方案。真可惜。我正在将我的类“转换”为命名空间并复制粘贴函数声明。我只需要一个基类和一个重新实现接口的子类。 C 函数指针 + C++ class= 对我来说是个坏消息。 无论如何感谢所有答案,您向我展示了 C++ 的一些黑暗角落;)

【问题讨论】:

  • fdatainput 参数是关于什么的?为什么被积函数类型中的void*参数没有名字?
  • Caspin,你不需要在类型声明中命名函数的参数。如果您不需要在函数中使用它们,您甚至不需要在函数的实际定义中命名它们。如果他们的函数需要匹配某个签名以与其他代码一起使用,人们通常会写这个,但有些参数永远不会在他们的实现中使用。
  • @A.Le:Caspin 知道这一点。问题是我们不知道void* fdata 是否被传递——如果integrandvoid* 参数被恰当地命名,我们就不必猜测/询问。
  • 啊!傻我!我把一个主要问题误认为是真正的无知。对不起卡斯平!
  • 我知道当我问它时,有人会向我解释为什么不需要它们。也许我应该在我的问题前加上“我知道……”。

标签: c++ c++11 tr1


【解决方案1】:

如果您只是想将成员函数传递给 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:不,这是完全安全、可移植和良好的 C++ 风格。我只使用静态函数来访问myIntegrator 的内部。如果我希望fancy_integrand 可以是一个独立的功能,并且myIntegrator 的所有成员都可以公开。我喜欢静态方法,因为它更加封装。
  • 虽然标题可能无法在没有绑定和/或函数的情况下给出答案,但它确实解决了手头的问题。谢谢你的坚持:)
【解决方案2】:

你有三个问题......首先你想要一个std::tr1::function&lt;R (Args..)&gt;,但你的问题归结为std::tr1::function&lt;R (*)(Args...)&gt; - 所以你需要两个typedef:

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

...所以第一个允许您编译function&lt;integrand&gt;adapt_integrate 必须相应地修复:

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

接下来你的bind 语法是关闭的,它应该是:

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

剩下的问题是tr1::function&lt;T&gt; 不能转换为函数指针,所以你必须通过一个包装函数,使用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】:

    由于 std::tr1::bind 和 c 风格的函数指针不能相处,所以试试这个。它会起作用,除了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* 参数没有通过我会感到惊讶:)
    • 如何删除像 calcTripleIntegral 这样的便利函数(它只定义/声明 val、err、input 以及 xMin 和 xMax 参数)使 std::tr1::bind 和函数变得多余?我可以删除它,但不知道它如何简化事情。我从来没有使用过这个 tr1 功能,也没有找到一个好的(非增强的)解释:(
    • 如果我尝试这个,我会收到关于 m_integrand 类型不完整的错误:(
    • @rub:正如我和其他人指出的那样,function 不采用函数 pointer 类型 - function&lt;void (*)(int)&gt; - 而是一个函数类型 - function&lt;void (int)&gt;。使用第一个会给你确切的错误。
    • @rubenvb: 从界面中删除caldTripleIntegral,同时从界面中删除std::tr1::bindstd::tr1::functiongetValue 没有说明它是如何实现的,两双一出。在内部,我可以(间接)将成员函数传递给updapt_integrate,而不使用std::tr1::bind
    【解决方案4】:

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

    你不能。如果您搜索 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 来解决这个问题的原因,例如stackoverflow.com/questions/2374847/…(第二个答案是我的灵感来源)
    • @rub:第二个答案不必通过 C 函数 / 作为普通函数指针传递 - 这就是问题所在。
    • 这个答案是正确的。我正在查看 void* 参数 - 这是用户数据样式的参数吗?许多 C 回调为您需要的任何上下文提供 void*。如果你的 C 函数指针没有多余的 void*,那么你不能这样做。
    【解决方案5】:

    假设 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 更灵活,并以这种方式传递任何你喜欢的东西。

    【讨论】:

      【解决方案6】:

      该错误消息听起来像是您缺少所涉及类型之一的包含。至少尝试仔细检查您的 integrandtr1 包含?

      【讨论】:

      • 我已经包含了 和 ,我还能包含什么? integrand 在 Cubature.h 中定义,我将其包含在内。这段独立的无类形式的代码可以正常工作,所以integrand 不是问题
      【解决方案7】:

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

      对于您的示例,这归结为(使用占位符)

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

      因为你绑定了一个成员函数,你有一个额外的(隐式)参数,即你调用成员函数的对象,所以你有六个。

      首先您绑定this 对象,对于其他参数您只需传递占位符。

      附带说明一下,您的成员函数返回 double,而函数声明返回 void。

      (作为记录,我仍在使用旧的编译器,几乎不支持 tr1,所以我只有 bindfunction 使用 boost 的经验,也许 tr1 的情况有所改变......)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-08-08
        • 1970-01-01
        相关资源
        最近更新 更多