C ++メソッドを、ポインター引数を持つC関数に変換することは受け入れ可能なパターンですか?


16

ESP-32でC ++を使用しています。タイマーを登録するとき、私はこれをしなければなりません:

timer_args.callback = reinterpret_cast<esp_timer_cb_t>(&SoundMixer::soundCallback);
timer_args.arg = this;

ここでタイマーが呼び出しますsoundCallback

タスクを登録するときも同じこと:

xTaskCreate(reinterpret_cast<TaskFunction_t>(&SoundProviderTask::taskProviderCode), "SProvTask", stackSize, this, 10, &taskHandle);

そのため、メソッドは個別のタスクで開始されます。

GCCは常にこれらの変換について警告しますが、計画どおりに機能します。

量産コードでは受け入れられますか?これを行うためのより良い方法はありますか?

回答:


47

あなたが何をしているかを正確にreinterpret_cast知らない限り、A は常に怪しげです。ここでは、C ++メソッドに対するGCCの呼び出し規則のためにのみコードが機能しますが、これは未定義の動作に非常に似ています。特に、メンバー関数が通常の関数ポインターと何らかの互換性があると想定しないでください。

通常のアプローチは、代わりに適切な署名を使用してC互換関数を定義し、内部でC ++メソッドを呼び出します。例えば:

extern "C" static void my_timer_callback(void* arg) {
  static_cast<SoundMixer*>(arg)->soundCallback();
}

このキャストは、a void*からポイント先オブジェクトのタイプにキャストするため、問題ありません。

詳細:

  • extern "C"この関数の言語リンケージを指定します。言語リンケージは、名前のマングリングと関数の呼び出し規則に影響します。メンバー関数にC言語リンケージを含めることはできません。言語リンケージは、内部/外部リンケージにほぼ直交しています。

  • コールバックの場合、関数は「プライベート」、つまり内部リンケージを持つ場合があります。Cコードは、名前でコールバックを参照することはありません。上記のコードスニペットstaticは、(静的メソッドではなく)キーワードを使用して内部リンケージを指定しています。または、関数を匿名の名前空間に配置することもできます。

    私は間の相互作用について完全にわからないextern "C"static(内部リンケージ)の。例えば、[dcl.link]私がそうすることを、この解釈「すべての関数型、外部リンケージを持つ関数名、および外部リンケージを持つ変数名は言語リンケージを持っている」と言うタイプのは、my_timer_callbackC言語のリンケージがありますが、その機能のことを名前ではありません。

  • static_castの実際の型はわかっているargが型システム内で表現できないため、ここでA が適切です。対照的reinterpret_castに、数値パターンへのポインタなど、ビットパターンを再解釈する場合は、a が適切です。

  • 関数は通常のオブジェクトではなく、メンバー関数はさらに少ないです。関数が実際の型を介してのみ呼び出される限り、関数ポインタ型の間で再解釈キャストすることができます(メンバー関数ポインタについても同様)。関数ポインターを他の型(オブジェクトポインターまたはvoidポインターなど)にキャストできるかどうかは、実装定義(バックグラウンド)です。POSIXでは、関数ポインター間のキャストvoid*が許可されているため、機能しますdlsym()。(メンバー)関数ポインターを含むその他のキャストは未定義です。特に、メンバー関数と関数ポインター間のキャストはできません。


1
std::bind最初のメソッドの引数としてオブジェクトポインターも想定していませんか?
ヴァルは、モニカを復活させる

5
@valはい、ただし、メンバー関数が通常の関数と互換性があるという意味ではありません。bind()はメンバー関数を通常の関数オブジェクトを含む別のケースとして扱うINVOKEアルゴリズムを使用するだけです。関数ポインタ。std :: bind()はファンクターを作成するため、Cとのインターフェースには適していません
amon

1
別の質問:なぜextern "C"ここに必要なのですか?この場合、Cリンケージは重要ですか?
ヴァルは、

5
@val Cからその関数を呼び出せるようにするには、Cの呼び出し規約を使用する必要があります。これは、C言語リンケージを使用してその関数を宣言するか、コンパイラ固有の拡張機能(など__attribute__((cdecl)))で実行できますが、実行しないでください。それ以外の場合、C ++関数は、C互換の呼び出し規約を持つことを保証されません(ただし、GCCでは通常はうまく機能します)。
アモン

4
@val extern "C"正式に必要な理由の詳細については、[dcl.link]「異なる言語リンケージを持つ2つの関数型は、他の点では同一であっても別個の型です」を参照してください。そして[expr.call]「その機能タイプのアンクルFiのNED行動に呼び出される関数のデFi回線nition結果の関数型からディFF erentで発現を介して機能を呼び出す」
ベン・フォークト

-1

個人的に、私が見つけた最も互換性があり、実装が容易で理解しやすいアプローチは、メソッドを内部的に呼び出す予想されるCインターフェイスと互換性のある「ラッパー」関数を提供することです(静的でない場合、インスタンス化するか、既存のインスタンスを使用してそれを行います)。これは、アダプター設計パターンの一種のバリエーションと見ることができます。


6
それはアモンが答えたものではありませんか?
ドロンツ

1
@Dronzは2回目の読み取りの後、はい、ほとんどそれです。私が読んだ途端にstaticそれをメソッドとして見たが、何らかの理由でthisポインタが最初の引数として渡されないことに気づかなかった(そして、std::bindそれを使用することに関する次の議論)。しかし、はい、あなたは絶対に正しいです!(二重回答ごめんなさい!)
イエスアロンソ

3
ええ、static少なくとも3つの異なる明確な意味があります。そして、注意しないとそれらを混ぜてしまいます。のそれぞれの使用法の違いを理解しておくと、staticそれぞれが優れたツールであるため、非常に役立ちます。
cmaster-モニカの復元
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.