7

コールバックに関数ポインタの構造体を使用する C ライブラリがあります。コールバックは C コードから呼び出されます。

extern "C" {
typedef struct callbacks_t {
    void (*foo) (const char*);
    int  (*bar) (int);  
} callbacks_t;
}// extern C

C ライブラリから呼び出される関数ポインタに安全に配置できる C++ 関数の種類は何ですか? 静的メンバー関数? 完全に指定されたテンプレート関数? 非キャプチャ ラムダ?

g++ では上記のすべてを使用できるように見えますが、C および C++ 関数に異なる呼び出し規則と言語バインディングが使用されている場合の安全性に疑問があります。

4

2 に答える 2

7

コールバックに関数ポインタの構造体を使用する C ライブラリがあります。コールバックは C コードから呼び出されます。

AC ライブラリは C のみを理解します。そのため、C によって明示的にサポートされ、理解されるもののみを返すことができます。

C++ には関数型の呼び出し規則の定義がないため、C++ 関数を明示的に戻すことはできません。C 関数 ( で明示的に宣言されたものextern "C") のみを返して、互換性があることを保証できます。

すべての未定義の動作と同様に、これは機能しているように見える場合があります (通常の C++ 関数または静的メンバーを戻すなど)。しかし、すべての未定義の動作と同様に、動作することが許可されています。それが実際に正しいことや移植可能であることを保証することはできません。

extern "C" {
    typedef struct callbacks_t {
        void (*foo) (const char*);
        int  (*bar) (int);  
    } callbacks_t;

    // Any functions you define in here.
    // You can set as value to foo and bar.

}// extern C

C ライブラリから呼び出される関数ポインタに安全に配置できる C++ 関数の種類は何ですか?

静的メンバー関数?

いいえ。しかし、これはたまたま多くのプラットフォームで機能します。これは一般的なバグであり、一部のプラットフォームでは人を悩ませます。

完全に指定されたテンプレート関数?

いいえ。

非キャプチャ ラムダ?

いいえ。

g++ では、上記のすべてを使用できるように見えますが、

はい。ただし、C++ コンパイラでビルドされたオブジェクトに渡すことを前提としています (これは合法です)。C++ コードは正しい呼び出し規約を使用します。問題は、これらのものを C ライブラリに渡すときです (pthreads が思い浮かびます)。

于 2016-04-29T21:17:38.800 に答える
3

一般に、キャストを使用しない限り、g++ を信頼する必要があります。

あなたが言及した関数型のどれもC内から使用するためにエクスポートできないことは事実ですが、これはあなたが求めているものではありません. 関数ポインタとして渡すことができる関数について尋ねています。

合格できることを答えるには、合格できないことを理解する方が建設的だと思います。引数リストに明示的に記載されていない追加の引数を必要とするものを渡すことはできません。

したがって、非静的メソッドはありません。暗黙の「これ」が必要です。Cはそれを渡すことを知りません。繰り返しますが、コンパイラは許可しません。

ラムダをキャプチャしません。それらは、実際のラムダ本体で暗黙の引数を必要とします。

渡すことができるのは、暗黙のコンテキストを必要としない関数ポインターです。実際のところ、あなたは先に進んでそれらをリストしました:

  • 関数ポインタ。テンプレートが完全に解決されている限り、それが標準関数であるかテンプレートであるかは関係ありません。これは問題ではありません。関数ポインターになる構文を作成すると、テンプレートが自動的に完全に解決されます。
  • 非キャプチャ ラムダ。これは、C++11 がラムダを導入したときに導入された特別な回避策です。そうすることが可能であるため、コンパイラはそれを実現するために必要な明示的な変換を行います。
  • 静的メソッド。それらは静的であるthisため、暗黙的な が渡されないため、問題ありません。

最後の 1 つは拡張に耐えます。多くの C コールバック メカニズムは、関数ポインタと void* opaq を取得します。以下は、C++ クラスでそれらを使用する標準であり、かなり安全です。

class Something {
  void callback() {
    // Body goes here
  }

  static void exported_callback(void *opaq) {
    static_cast<Something*>(opaq)->callback();
  }
}

そして、次のようにします。

Something something;

register_callback(Something::exported_callback, &something);

編集して追加: これが機能する唯一の理由は、暗黙の引数が渡されない場合、C++ 呼び出し規約と C 呼び出し規約が同一であるためです。名前マングリングには違いがありますが、名前マングリングの唯一の目的はリンカーが正しい関数のアドレスを見つけられるようにすることであるため、関数ポインターを渡す場合は関係ありません。

stdcall や pascal 呼び出し規則などを期待するコールバックでそのトリックを試した場合、このスキームは表面上失敗するでしょう。

ただし、これは静的メソッド、ラムダ、およびテンプレート関数に固有のものではありません。そのような状況では、標準関数でさえ失敗します。

悲しいことに、stdcall 型への関数ポインターを定義すると、gcc は無視します。

#define stdcall __attribute__((stdcall))
typedef stdcall void (*callback_type)(void *);

結果:

test.cpp:2:45: warning: ‘stdcall’ attribute ignored [-Wattributes]
 typedef stdcall void (*callback_type)(void *);
于 2016-04-29T17:34:49.090 に答える