45

最初のパラメーターとして関数ポインターを受け取るメソッドを持つサードパーティのライブラリがあります。

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>>
]

メンバーを関数に渡す方法はありますか?

4

4 に答える 4

22

メンバーを関数に渡す方法はありますか?

クラスオブジェクトがある種のグローバルオブジェクトでない限り、それは不可能です。オブジェクトには何らかのデータが含まれている可能性があるため、関数ポインターは関数へのポインターにすぎません。実行時のコンテキストは含まれず、コンパイル時のコンテキストのみが含まれます。

コールバックが渡されるたびにコンパイル時の一意の 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 は必要ない場合があります。たとえば、ファンクターが既に異なる型を持っている場合や、それらの使用のランタイム スコープが重複していない場合などです。ただし、毎回一意の ID を使用する方が安全です。

実装:

ライブデモ

#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]) ));
}

PSマルチスレッドアクセスに注意してください-そのような場合、ある種のスレッドローカルストレージデータを使用する必要があります。

于 2012-11-05T18:29:01.660 に答える
6

サードパーティの関数が関数へのポインタを期待しているためコンパイルされませんが、メンバーへのポインタ関数を渡そうとしています。2つのタイプは根本的に異なり、交換することはできません。実際、メンバー関数へのポインターは、非常に多くの場合、奇妙な動物です。

これがあなたが抱えている問題を説明する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();
    }
};
于 2012-11-05T18:51:40.380 に答える
6

他の人が言ったように、グローバルまたは静的データを使用してバインド呼び出しコンテキストを生の関数として提供する以外に選択肢はありません。しかし、提供された解決策は一般的ではなく、ファンクターの空のパラメーターリストで立ち往生しています。wrapper手動で記述する必要がありget_wrapperFuncバインドする関数シグネチャごとに異なる名前を付けます。

raw バインドのより一般化されたソリューションを提案したいと思います。

#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!");
}

テスト済み -ここでライブ アクションを参照

于 2012-11-07T22:44:08.590 に答える
1

いいえ、簡単ではありません。問題は、関数ポインターが 1 つの情報チャンク (関数のアドレス) を持っていることです。メソッドには、それとオブジェクトのアドレスの両方が必要です。または、オブジェクトのアドレスをパラメーターとして渡すこともできます。

これを行うには非常にハックな方法がありますが、それらはプラットフォーム固有のものになります。そして非常にハッキー。グローバル変数を使用する代わりに、グローバル変数をお勧めします。

クラスのグローバル インスタンスが 1 つしかない場合、これを行う方法を知っていますよね?

于 2012-11-05T18:32:25.047 に答える