2013-09-27 11 views
6

私はいくつかのパラメータを最初にバインドすることによっていくつかのロジックを抽象化しながらコールバックを提供するためにstd :: bindを使用しています。すなわちCでのstd :: bindのエミュレート

void start() { 

    int secret_id = 43534; 

    //Bind the secret_id to the callback function object 
    std::function<void(std::string)> cb = std::bind(&callback, secret_id, std::placeholders::_1); 

    do_action(cb); 

} 

void do_action(std::function<void(std::string)> cb) { 

    std::string result = "hello world"; 
    //Do some things... 

    //Call the callback 
    cb(result); 
} 

void callback(int secret_id, std::string result) { 

    //Callback can now do something with the result and secret_id 

} 

したがって、上記の例では、do_actionはsecret_idについて知る必要はありませんし、他の機能は、独自のsecret_idを持たずに、それを再利用することができます。これは、do_actionが何らかの非同期操作である場合に特に便利です。

私の質問は、Cのみを使用して関数ポインタにパラメータ値をバインドする方法はありますか?

std :: bindをエミュレートしないと、ニュートラルdo_action()を複雑にすることなく、first()からcallback()にデータを渡す別の方法がありますか?

答えて

12

番号Cでは、直接行うことはできません。 Cでは

コールバックを処理するための標準的な方法は、コンテキストポインタを使用している:

void register_callback(void (*cback)(void *context, int data), 
         void *context); 

これはあなたがコールバックが(中に処理する必要があり、通常のパラメータに加えvoid *を受け入れる関数を渡すことを意味します上記の場合は整数)、返される値はvoid *です。

このvoid *は通常、コールバックに必要なすべての追加パラメータまたはデータを含むstructを指します。このアプローチを使用すると、ライブラリはこのコンテキストが何であるかに依存しません。コールバックにコンテキストが必要ない場合は、NULLポインタをcontextとして渡し、ライブラリから呼び出されるときに最初のパラメータを無視します。

ハックされ、正式に安全ではないものの、文脈がvoid *(たとえば整数)のサイズに適合する単純なデータであり、環境に問題がない場合ライブラリーから呼び出されたときに偽のvoid *を整数に戻してライブラリーをトリックすることができます。これにより、呼び出し側がコンテキストを割り振り、そのライフタイムを管理することがなくなります。この制限を回避するために、言語をだまし方に(まだポータブルCの土地に残っている)私はいくつかのハックを考えることができる方法についての

まず我々は、二引数のコールバックとコンテキストデータのプールを割り当てる

void (*cbf[6])(int, int); 
int ctx[6]; 

次に、登録したい2つの引数のバージョンを呼び出す関数を記述します(またはマクロ生成する)。

void call_with_0(int x) { cbf[0](ctx[0], x); } 
void call_with_1(int x) { cbf[1](ctx[1], x); } 
void call_with_2(int x) { cbf[2](ctx[2], x); } 
void call_with_3(int x) { cbf[3](ctx[3], x); } 
void call_with_4(int x) { cbf[4](ctx[4], x); } 
void call_with_5(int x) { cbf[5](ctx[5], x); } 

我々はまた、それらが割り当てられているプールに保存し、割り当て解除:

int first_free_cback = 0; 
int next_free_cback[6] = {1, 2, 3, 4, 5, -1}; 

void (*cbacks[6])(int) = { call_with_0, 
          call_with_1, 
          call_with_2, 
          call_with_3, 
          call_with_4, 
          call_with_5 }; 

をその後、我々は

void (*bind(void (*g)(int, int), int v0))(int) 
{ 
    if (first_free_cback == -1) return NULL; 
    int i = first_free_cback; 
    first_free_cback = next_free_cback[i]; 
    cbf[i] = g; ctx[i] = v0; 
    return cbacks[i]; 
} 

ような何かを行うことができ、最初のパラメータが、バインドされた関数をバインドします明示的に割り当て解除する必要があります

int deallocate_bound_cback(void (*f)(int)) 
{ 
    for (int i=0; i<6; i++) { 
     if (f == cbacks[i]) { 
      next_free_cback[i] = first_free_cback; 
      first_free_cback = i; 
      return 1; 
     } 
    } 
    return 0; 
} 
3

短い答えはいいえです。

唯一できることは、secret_idが組み込まれている別の関数を宣言することです。 C99以降を使用している場合は、関数呼び出しのオーバーヘッドを最小限に抑えるためにインライン関数にすることができますが、新しいコンパイラではそれだけで可能です。

std :: bindはテンプレート化された構造体を返すので、std :: bindはsecret_idが組み込まれた新しいファンクタを宣言するだけです。

4

6502は説明したように、コールバックに何らかの種類のコンテキスト引数を渡すことなく、移植可能なCでこれを行うことはできません。たとえそれがsecret_idという名前ではないとしてもです。しかし、移植性のない手段による追加情報(クロージャ)を伴うC関数の作成を可能にするBruno Haible's trampolineなどのライブラリがあります。これらのライブラリは、アセンブリやコンパイラ拡張を呼び出すことによって魔法を発揮しますが、多くの一般的なプラットフォームに移植されています。彼らがアーキテクチャをサポートしていればが気になりますが、うまく動作します。 the webから撮影

は、ここにトランポリンが可能であることがコードの例であるパラメータaを取り、この高階関数である、b、およびc(あなたsecret_idに類似した、とa*x^2 + b*x + cを計算し、正確に一つのパラメータxの機能を返します:ソースの

#include <trampoline.h> 

static struct quadratic_saved_args { 
    double a; 
    double b; 
    double c; 
} *quadratic_saved_args; 

static double quadratic_helper(double x) { 
    double a, b, c; 
    a = quadratic_saved_args->a; 
    b = quadratic_saved_args->b; 
    c = quadratic_saved_args->c; 
    return a*x*x + b*x + c; 
} 

double (*quadratic(double a, double b, double c))(double) { 
    struct quadratic_saved_args *args; 
    args = malloc(sizeof(*args)); 
    args->a = a; 
    args->b = b; 
    args->c = c; 
    return alloc_trampoline(quadratic_helper, &quadratic_saved_args, args); 
} 

int main() { 
    double (*f)(double); 
    f = quadratic(1, -79, 1601); 
    printf("%g\n", f(42)); 
    free(trampoline_data(f)); 
    free_trampoline(f); 
    return 0; 
} 
+0

"C"でこれを実現する方法は確かです。 LibFFIはクロージャを提供し、ほとんどの主要OSに移植可能です:http://sourceware.org/libffi/ –

+0

@BojanNikolic良い点、libffiはトランポリンよりもはるかに一般的です。 libffiを使用してOPの問題を解決するnaという例の答えが評価されるかもしれません。 – user4815162342

3

不透明タイプと保つ秘密はそれを行う必要があります。

#include <stdio.h> 

// Secret.h 

typedef struct TagSecret Secret; 
typedef void (*SecretFunction)(Secret*, const char* visible); 
void secret_call(Secret*, const char* visible); 

// Public.c 

void public_action(Secret* secret, const char* visible) { 
    printf("%s\n", visible); 
    secret_call(secret, visible); 
} 


// Secret.c 

struct TagSecret { 
    int id; 
}; 

void secret_call(Secret* secret, const char* visible) { 
    printf("%i\n", secret->id); 
} 

void start() { 
    Secret secret = { 43534 }; 
    public_action(&secret, "Hello World"); 
} 


int main() { 
    start(); 
    return 0; 
} 

(上記はコールバック関数の登録には対処していません)

関連する問題