web-dev-qa-db-ja.com

C ++メソッドを、ポインター引数を持つC関数に変換することは、許容できるパターンですか?

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は常にこれらの変換について警告しますが、計画どおりに機能します。

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

_reinterpret_cast_は、何をしているのかexactlyを除いて、常にうさんくさいです。ここでは、CCCメソッドのGCCの呼び出し規約が原因でコードがたまたま動作しますが、これは未定義の動作と非常に似ています。特に、メンバー関数が通常の関数ポインターと何らかの形で互換性があると想定しないでください。

通常のアプローチは、代わりに適切なシグネチャを持つC互換の関数を定義することです。これは、内部でC++メソッドを呼び出します。例えば:

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

_void*_からポイントされたオブジェクトの型にキャストバックしているため、このキャストは問題ありません。

詳細:

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

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

    _extern "C"_とstatic(内部リンケージ)の相互作用については、完全にはわかりません。例えば。 _[dcl.link]_は、「すべての関数型、外部リンケージのある関数名、および外部リンケージのある変数名には言語リンケージがある」と述べています。私はこれを解釈して、_my_timer_callback_のtypeにC言語リンケージがあるが、その関数名がはしません。

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

  • 関数は通常のオブジェクトではなく、メンバー関数もそうではありません。関数がその実際の型を介してのみ呼び出される限り(および同様にメンバー関数ポインターの場合)、関数ポインター型の間でキャストを再解釈できます。関数ポインターを他の型(オブジェクトポインターやvoidポインターなど)にキャストできるかどうかは、実装によって定義されます( background )。 POSIXでは、dlsym()が機能するように、関数ポインターと_void*_間のキャストが許可されています。 (メンバー)関数ポインターを含むその他のキャストは未定義です。特に、メンバー関数と関数ポインター間のキャストはできません。

47
amon