【问题标题】:convert std::bind to function pointer将 std::bind 转换为函数指针
【发布时间】:2012-10-25 15:21:03
【问题描述】:

我有一个第三方库,它有一个将函数指针作为第一个参数的方法:

int third_party_method(void (*func)(double*, double*, int, int, double*), ...);

我想传递一个指向类方法的指针,声明如下:

class TestClass
{
    public:
        void myFunction (double*, double*, int, int, void*);

我尝试如下传递这个函数:

TestClass* tc = new TestClass();
using namespace std::placeholders;
third_party_method(std::bind(&TestClass::myFunction, tc, _1, _2, _3, _4, _5), ...);

但是,这不会编译:

Conversion of parameter 1 from 'std::tr1::_Bind<_Result_type,_Ret,_BindN>' to 'void (__cdecl *)(double *,double *,int,int,void *)' is not possible
with
[
    _Result_type=void,
    _Ret=void,
    _BindN=std::tr1::_Bind6<std::tr1::_Callable_pmf<void (__thiscall TestClass::* const )(double *,double *,int,int,void *),TestClass,false>,TestClass *,std::tr1::_Ph<1>,std::tr1::_Ph<2>,std::tr1::_Ph<3>,std::tr1::_Ph<4>,std::tr1::_Ph<5>>
]

有什么方法可以将成员传递给函数吗?

【问题讨论】:

  • 没有。您需要编写一个具有所需签名的函数。你不能从函数对象中获得一个指针。
  • 不可能 - 见question
  • 如果您的第三方库要求您提供回调而没有提供某种不透明上下文参数的功能,那么您基本上注定要失败。如果对该库的访问是单线程的,或者由持有回调函数的对象实例进行多线程的访问在运行时从不改变,则可以使用普通函数和指向类实例的静态指针。责备第三方;如果你已经付钱给他们,你可能会为此而麻烦他们。
  • 对于其他从 C 转向 C++ 的人:不要将回调定义为函数指针,而是使用 functors。查找std::function。然后您就可以将this 指针绑定到一个方法,并将其作为“函数指针”(函子)传递。

标签: c++ function-pointers


【解决方案1】:

正如其他人提到的,您别无选择,只能使用全局或静态数据来提供绑定调用上下文作为原始函数。但是提供的解决方案并不通用,它卡在仿函数的空参数列表中。您需要为要绑定的每个不同的函数签名手动编写wrapperget_wrapperFunc,并为它们指定不同的名称。

我想为原始绑定提出更通用的解决方案:

#include <iostream>
#include <memory>
#include <functional>
#include <cassert>

// Raw Bind - simulating auto storage behavior for static storage data
template <typename BindFunctor, typename FuncWrapper> class scoped_raw_bind
{
public:

   typedef scoped_raw_bind<BindFunctor, FuncWrapper> this_type;

   // Make it Move-Constructible only
   scoped_raw_bind(const this_type&) = delete;
   this_type& operator=(const this_type&) = delete;
   this_type& operator=(this_type&& rhs) = delete;

   scoped_raw_bind(this_type&& rhs): m_owning(rhs.m_owning)
   {
      rhs.m_owning = false;
   }

   scoped_raw_bind(BindFunctor b): m_owning(false)
   {
      // Precondition - check that we don't override static data for another raw bind instance
      if(get_bind_ptr() != nullptr)
      {
        assert(false);
        return;
      }
      // Smart pointer is required because bind expression is copy-constructible but not copy-assignable
      get_bind_ptr().reset(new BindFunctor(b));
      m_owning = true;
   }

   ~scoped_raw_bind()
   {
     if(m_owning)
     {
        assert(get_bind_ptr() != nullptr);
        get_bind_ptr().reset();
     }
   }

   decltype(&FuncWrapper::call) get_raw_ptr()
   {
      return &FuncWrapper::call;
   }

   static BindFunctor& get_bind()
   { 
      return *get_bind_ptr();
   }

private:

  bool m_owning;

  static std::unique_ptr<BindFunctor>& get_bind_ptr()
  {
     static std::unique_ptr<BindFunctor> s_funcPtr;
     return s_funcPtr;
  }

};

// Handy macro for creating raw bind object
// W is target function wrapper, B is source bind expression
#define RAW_BIND(W,B) std::move(scoped_raw_bind<decltype(B), W<decltype(B), __COUNTER__>>(B));

// Usage
///////////////////////////////////////////////////////////////////////////

// Target raw function signature
typedef void (*TargetFuncPtr)(double, int, const char*);

// Function that need to be called via bind
void f(double d, int i, const char* s1, const char* s2)
{
   std::cout << "f(" << d << ", " << i << ", " << s1 << ", " << s2 << ")" << std::endl;
}

// Wrapper for bound function
// id is required to generate unique type with static data for
// each raw bind instantiation.
// THE ONLY THING THAT YOU NEED TO WRITE MANUALLY!
template <typename BindFunc, int id = 0> struct fWrapper
{
   static void call(double d, int i, const char* s)
   {
      scoped_raw_bind<BindFunc, fWrapper<BindFunc, id>>::get_bind()(d, i, s);
   }
};

int main()
{
   using namespace std::placeholders;

   auto rf1 = RAW_BIND(fWrapper, std::bind(&f, _1, _2, _3, "This is f trail - 1"));
   TargetFuncPtr f1 = rf1.get_raw_ptr();
   f1(1.2345, 42, "f1: Bind! Bind!");

   auto rf2 = RAW_BIND(fWrapper, std::bind(&f, _1, _2, _3, "This is f trail - 2"));
   TargetFuncPtr f2 = rf2.get_raw_ptr();
   f2(10.2345, 420, "f2: Bind! Bind!");

   auto rf3 = RAW_BIND(fWrapper, std::bind(&f, _1, _2, _3, "This is f trail - 3"));
   TargetFuncPtr f3 = rf3.get_raw_ptr();
   f3(100.2345, 4200, "f3: Bind! Bind!");
}

已经过测试 - 请参阅 Live Action Here

【讨论】:

    【解决方案2】:

    有什么方法可以将成员传递给函数吗?

    除非你的类对象是某种全局对象——这是不可能的。因为对象可能包含一些数据,而函数指针只是指向函数的指针——它不包含任何运行时上下文,只有编译时上下文。

    如果您接受每个回调传递的编译时唯一 ID,那么您可以使用以下通用方法。

    用法:

    void test(void (*fptr)())
    {
        fptr();
    }
    
    struct SomeStruct
    {
        int data;
        void some_method()
        {
            cout << data << endl;
        }
        void another_method()
        {
            cout << -data << endl;
        }
    };
    
    int main()
    {
        SomeStruct local[] = { {11}, {22}, {33} };
    
        test(get_wrapper<0>(  boost::bind(&SomeStruct::some_method,local[0]) ));
        test(get_wrapper<1>(  boost::bind(&SomeStruct::another_method,local[0]) ));
    
        test(get_wrapper<2>(  boost::bind(&SomeStruct::some_method,local[1]) ));
        test(get_wrapper<3>(  boost::bind(&SomeStruct::another_method,local[1]) ));
    
        test(get_wrapper<4>(  boost::bind(&SomeStruct::some_method,local[2]) ));
        test(get_wrapper<5>(  boost::bind(&SomeStruct::another_method,local[2]) ));
    }
    

    它可能不需要每次调用的唯一 ID,例如因为 Functor 可能已经有不同的类型,或者它们的使用的运行时范围不重叠。但每次都使用唯一 ID 更安全。

    实施:

    live demo

    #include <boost/optional.hpp>
    #include <boost/bind.hpp>
    #include <iostream>
    #include <ostream>
    using namespace std;
    
    template<unsigned ID,typename Functor>
    boost::optional<Functor> &get_local()
    {
        static boost::optional<Functor> local;
        return local;
    }
    
    template<unsigned ID,typename Functor>
    typename Functor::result_type wrapper()
    {
        return get_local<ID,Functor>().get()();
    }
    
    template<typename ReturnType>
    struct Func
    {
        typedef ReturnType (*type)();
    };
    
    template<unsigned ID,typename Functor>
    typename Func<typename Functor::result_type>::type get_wrapper(Functor f)
    {
        (get_local<ID,Functor>()) = f;
        return wrapper<ID,Functor>;
    }
    
    // ----------------------------------------------------------------------
    
    void test(void (*fptr)())
    {
        fptr();
    }
    
    struct SomeStruct
    {
        int data;
        void some_method()
        {
            cout << data << endl;
        }
        void another_method()
        {
            cout << -data << endl;
        }
    };
    
    int main()
    {
        SomeStruct local[] = { {11}, {22}, {33} };
    
        test(get_wrapper<0>(  boost::bind(&SomeStruct::some_method,local[0]) ));
        test(get_wrapper<1>(  boost::bind(&SomeStruct::another_method,local[0]) ));
    
        test(get_wrapper<2>(  boost::bind(&SomeStruct::some_method,local[1]) ));
        test(get_wrapper<3>(  boost::bind(&SomeStruct::another_method,local[1]) ));
    
        test(get_wrapper<4>(  boost::bind(&SomeStruct::some_method,local[2]) ));
        test(get_wrapper<5>(  boost::bind(&SomeStruct::another_method,local[2]) ));
    }
    

    附:注意多线程访问 - 在这种情况下,您应该使用某种 Thread-local storage 数据。

    【讨论】:

    • 谢谢。我会考虑这种方法。
    • 好极了。我正在考虑类似的事情,但现在有你的解决方案!
    • here 是基于BOOST_PP_COUNTER 的unique_ids 版本
    • @DmitryLedentsov,我想过。但是,请注意 - 不同的翻译单元可能具有相同的计数器编号 - 所以我更喜欢使用显式 ID。只是为了您的兴趣 - 有基于编译器相关错误的编译时间计数器的实现,没有宏:rsdn.ru/forum/cpp/2337951.flat
    【解决方案3】:

    它无法编译,因为第三方函数需要一个指向函数的指针,但您正试图向它传递一个指向-成员-函数的指针。两种类型为fundamentally different,不能互换。事实上,指向成员函数的指针通常是strange animals

    这是一个SSCCE 说明您遇到的问题:

    #include <iostream>
    #include <iomanip>
    using namespace std;
    
    typedef void(*SpeakFn)(void);
    
    void Bark()
    {
        cout << "WOOF" << endl;
    }
    
    void Meow()
    {
        cout << "meeow" << endl;
    }
    
    void SpeakUsing(SpeakFn fn)
    {
        fn();
    }
    
    class Alligator
    {
    public:
        void Speak()
        {
            cout << "YAWWW" << endl;
        }
        typedef void(Alligator::*AlligatorSpeakFn)(void);
    
        void SpeakUsing(AlligatorSpeakFn fn)
        {
            (this->*fn)();
        }
    };
    
    int main()
    {
        SpeakUsing(&Bark); // OK
    
        Alligator a;
        Alligator::AlligatorSpeakFn mem_fn = &Alligator::Speak;
        a.SpeakUsing(mem_fn);   // OK
    
        SpeakUsing(mem_fn); // NOT OK -- can't cvt from fn-ptr to mem-fn-ptr
    }
    

    您不能使用指向成员函数的指针调用SpeakUsing,因为它不能转换为指向函数的指针。

    改用静态成员函数,如:

    class Alligator
    {
    public:
        static void Speak()
        {
            cout << "YAWWW" << endl;
        }
        typedef void(*AlligatorSpeakFn)(void);
    
        void SpeakUsing(AlligatorSpeakFn fn)
        {
            fn();
        }
    };
    

    【讨论】:

      【解决方案4】:

      不,不容易。问题是函数指针有一大块信息——函数的地址。一个方法既需要它又需要对象的地址,或者它可以将对象地址作为参数传递。

      有一些非常老套的方法可以做到这一点,但它们将是特定于平台的。而且非常hackey。以至于我会推荐全局变量而不是使用它们。

      如果该类只有一个全局实例,您确实知道该怎么做,对吧?

      【讨论】:

      • 是的,我知道。虽然全局变量和函数不太适合我们的架构。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-08-08
      • 2021-12-22
      • 1970-01-01
      • 1970-01-01
      • 2019-12-17
      • 2012-11-21
      相关资源
      最近更新 更多