web-dev-qa-db-ja.com

Cでキャプチャされたデータを使用した動的ディスパッチ?

オブジェクト指向/機能的なマインドとC言語でのプログラミングを両立させようとしています。Cで動的ディスパッチを実現したいとしましょう。たとえば、実行するタスクのコレクションが欲しいとしましょう。実行したい関数のコレクションへのポインターを簡単に作成できます。ただし、実行されるタスクは、関数が作成されたときに存在する変数に依存し、それらを実行するものによって関数に渡されないとします。 JavaまたはC++では、これらの変数をキャプチャするオブジェクト(おそらくラムダ)を使用して、関数が実行中にそれらにアクセスできるようにします。しかし、関数ポインターは変数をキャプチャできないと聞きました標準Cでは、このデザインパターンをどのように実装すればよいでしょうか。

4
Phoenix

CS StackExchangeで 関数ポインターの観点から高次関数をエンコードする方法を説明しました 。基本的に、クロージャは環境へのvoid *と関数ポインタのペアにすぎません。何かのようなもの:

struct IntToIntClosure {
    void *environment;
    int (*func)(void *, int);
}

たとえば、Linux非同期I/O APIでは、 sigevent 構造体がこれらのものをペアにします(「環境」はsigev_valueフィールドです)。

ただし、foo *を配列として表示するときにfoo *と長さを別々に渡すのが一般的であるように、実際にはこれらをデータ構造に一緒にパッケージ化せず、次のように渡すのが一般的です個別のパラメータ。コールバックを受け入れる多くのC APIは、通常はvoid *である追加の「コンテキスト」パラメーターを受け取ります。このパラメーターは、呼び出されるたびに関数ポインターに渡されます。たとえば、glibcの on_exitatexit と対比)。

これらの手法を使用すると、動的ディスパッチで高次関数とオブジェクトを簡単にエンコードできます。ただし、登録用の関数ポインターのみを受け入れるAPIがある場合、これを行うことはできません。*。事後の関数ポインターのどの呼び出しにどの環境を関連付ける必要があるかを知る方法はありません。この場合、グローバル変数または静的変数を使用して、付随する制限のある環境情報を、許可されている基本的に1つの環境にのみ渡す必要があります。このAPIを制御している場合、私はstronglyこの環境用の追加のパラメーターを(明示的にまたはstruct)。

* 移植性を気にしない場合、これを回避するためのトリックがあります。関数ポインターを動的に生成できる場合(つまり、実行時にマシンコードを作成することにより)、目的の関数に追加のパラメーターを渡すスタブへの関数ポインターを作成できます。 これは、GHC HaskellがHaskell関数をCコールバックとして渡す方法です

Cではキャプチャの可能性はなく、関数は「作成」されませんが、実行可能イメージがメモリにロードされるため、存在します。

基本的に、やりたいことを行うには3つの方法があります。

  • 値とポインターを指す関数へのポインターを渡すためにグローバル変数を使用します。不便:カプセル化が不適切です。
  • 静的変数は、初期化関数と呼び出されるターゲット関数と共に、別のコンパイル単位で使用します。これは、カプセル化の観点からはより良い方法です。ただし、「キャプチャされた」変数のセットは1つに制限されています。
  • コンテキストメモリを使用します。これは前のアプローチの変形ですが、静的変数の代わりに、動的に割り当てられた構造体のメンバーを使用します。このコンテキストへのポインターは、初期化関数によって返され、関数ポインターと共に格納されます。これはリエントラントソリューションであり、適切なカプセル化を保証します。ただし、これには、呼び出される関数にポインターが渡される必要があります。もちろん、さまざまな関数がさまざまなコンテキスト構造を持つことができます(キャストの対象)
4
Christophe