web-dev-qa-db-ja.com

関数ポインタとしてファンクタを渡す

C++アプリでCライブラリを使用しようとしていますが、次の状況で自分を見つけました(Cは知っていますが、C++はかなり新しいです)。 C側には、関数ポインターを引数として取る関数のコレクションがあります。 C++側には、C関数に必要な関数ポインターと同じシグネチャーを持つファンクターを持つオブジェクトがあります。 C関数に渡す関数ポインターとしてC++ファンクターを使用する方法はありますか?

41
dagw

C++ファンクタオブジェクトへのポインタを、Cコード(またはC++コード)への関数ポインタとして直接渡すことはできません。

さらに、移植可能にコールバックをCコードに渡すには、少なくとも_extern "C"_非メンバー関数として宣言する必要があります。少なくとも、一部のAPIには特定の関数呼び出し規則が必要であり、追加の宣言修飾子が必要です。

多くの環境では、CとC++の呼び出し規約は同じであり、名前のマングリングのみが異なるため、グローバル関数または静的メンバーが機能します。ただし、operator()の呼び出しを通常の関数でラップする必要があります。

  • ファンクタに状態がない場合(それは、いくつかの正式な要件などを満たすためのオブジェクトです):

    _class MyFunctor {
      // no state
     public:
      MyFunctor();
      int operator()(SomeType &param) const;
    }
    _

    ファンクタを作成してそのoperator()を実行する通常のextern "C"関数を記述できます。

    _extern "C" int MyFunctorInC(SomeType *param)
    {
      static MyFunctor my_functor;
      return my_functor(*param);
    }
    _
  • ファンクターに状態がある場合、例えば:

    _class MyFunctor {
      // Some fields here;
     public:
      MyFunctor(/* some parameters to set state */);
      int operator()(SomeType &param) const;
      // + some methods to retrieve result.
    }
    _

    and Cコールバック関数は、ある種のユーザー状態パラメーター(通常はvoid *)を取ります。

    _void MyAlgorithmInC(SomeType *arr,
                        int (*fun)(SomeType *, void *),
                        void *user_state);
    _

    状態パラメーターをファンクターオブジェクトにキャストする通常のextern "C"関数を記述できます。

    _extern "C" int MyFunctorInC(SomeType *param, void *user_state)
    {
      MyFunctor *my_functor = (MyFunctor *)user_state;
      return (*my_functor)(*param);
    }
    _

    次のように使用します。

    _MyFunctor my_functor(/* setup parameters */);
    MyAlgorithmInC(input_data, MyFunctorInC, &my_functor);
    _
  • それ以外の場合、それを行う唯一の通常の方法(「実行時にマシンコードを生成しない」などの通常の方法)は、何らかの静的(グローバル)またはスレッドローカルストレージを使用してファンクターを外部の「C」関数に渡すことです。これは、コードで実行できることを制限し、醜いですが動作します。

54

私はこの " gem "をグーグルを使って見つけました。どうやら可能ですが、お勧めしません。直接 link をサンプルのソースコードに。

7
Andreas Brinck

いいえ、もちろんです。 C関数のシグネチャは、関数として引数を取ります。

void f(void (*func)())
{
  func(); // Only void f1(), void F2(), ....
}

ファンクタを使用するすべてのトリックはtemplate関数で使用されます。

template<class Func>
void f (Func func)
{
    func(); // Any functor
}
5
Alexey Malistov

C++で記述されたCコールバック関数はextern "C"関数として宣言する必要があるため、ファンクタを直接使用することはできません。そのコールバックとして使用するラッパー関数を記述し、そのラッパーにファンクターを呼び出させる必要があります。もちろん、コールバックプロトコルは、関数にコンテキストを渡すための何らかの方法が必要です。そうしないと、関数がファンクタに到達したり、タスクが非常に複雑になったりします。ほとんどのコールバックスキームにはコンテキストを渡す方法がありますが、私はそうではないいくつかの頭の痛いものを扱ってきました。

詳細については、この回答を参照してください(そして、コールバックが静的メンバー関数だけではなくextern "C"でなければならないという事例証拠については、コメントを参照してください)。

3
Michael Burr

私はあなたができるとは思いません:関数オブジェクトのoperator()は実際にはメンバー関数であり、Cはそれらについて何も知りません。

使用できるのは、無料のC++関数、またはクラスの静的関数です。

2
UncleBens

GCCでは、メンバー関数ポインターをプレーン関数ポインターに変換できます(プレーン関数ポインターによって呼び出される関数の最初の引数はthisです)。

マニュアルの各リンク を確認してください。

これには、明らかに非標準の機能に対するそれぞれの警告を抑制するために、_-Wno-pmf-conversions_フラグが必要です。 CスタイルライブラリとC++スタイルプログラミングのインターフェイスに非常に便利です。メンバー関数のポインターが定数の場合、コードを生成する必要はまったくありません。APIはとにかくその引数の順序を使用します。

ファンクタが既にある場合、そのようにファンクタをフラット化することは、そのoperator()をフラット化することを意味し、ファンクタクラスポインタ自体を最初の引数として呼び出す必要がある関数を提供します。これは必ずしもそれほどすべてを助けるわけではありませんが、少なくともCリンケージがあります。

しかし、少なくともファンクタを使用していない場合、これは役に立ち、_std::mem_fn_から_<functional>_を意味のあるCリンケージに置き換えることができます。

1
user3996901

関数ポインターコールバックを受け取る多くのC APIには、ユーザー状態用のvoid *パラメーターがあります。それらのいずれかを持っている場合、あなたは幸運です-ユーザーデータをある種の参照またはキーとして扱うexterm C関数を使用してファンクターを検索し、それを実行できます。

そうでなければ、いいえ。

0
JoeG

これが静的メソッドであるかインスタンスメソッドであるかによって異なります。静的メソッドである場合は、関数をclassName :: functionNameとして渡すことができます。インスタンスメソッドの場合は、明らかに特定のインスタンスに関連付ける必要があるため、かなり複雑です。しかし、C#などでデリゲートを使用する場合と同じようにはできません。

これを実行するために見つけた最良の方法は、オブジェクトのインスタンスと関数ポインターでインスタンス化される保持クラスを作成することです。保持クラスは、関数を直接呼び出すことができます。

0
Tim Ebenezer

C++ファンクタにはメンバー関数であるオーバーロードされた演算子()があり、メンバー関数ポインターが必要になるため、私はノーと言います。これは、クラスのインスタンスなしでは呼び出せないため、通常のC関数ポインターとはまったく異なるデータ型です。通常の関数または静的メンバー関数をCライブラリに渡す必要があります。オーバーロードされた()演算子は静的であってはならないため、実行できません。 Cライブラリには、通常の非メンバー関数または静的メンバー関数を渡す必要があります。そこからC++ファンクタを呼び出すことができます。

0
Charles Salvia

うーん、多分あなたはあなたの関数オブジェクトを包む無料のテンプレート関数を書くことができるでしょう。それらがすべて同じ署名を持っている場合、これは機能するはずです。このように(テストされていません):

template<class T>
int function_wrapper(int a, int b) {
    T function_object_instance;

    return funcion_object_instance( a, b );
}

これは、2つのintを取り、intを返すすべての関数に適用されます。

0
Björn Pollex