Emulating std::bind in C

前端 未结 4 1753
北荒
北荒 2021-01-12 16:43

I\'m using std::bind to provide a callback while abstracting some logic by binding some parameters first. i.e.

void start() {

    int secret_id = 43534;

           


        
4条回答
  •  长发绾君心
    2021-01-12 17:22

    No. C doesn't allow you to do that directly.

    In C the standard way to handle callbacks is using context pointers:

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

    this means that you will pass a function that will accept a void * in addition to the normal parameters that the callback should handle (in the above case an integer) and you will also pass a void * that you want to be passed back.

    This void * normally points to a struct that will contain all the extra parameters or data you need in the callback and using this approach the library doesn't depend on what this context is. If the callback doesn't need any context you just pass a NULL pointer as context and ignore the first parameter when being called from the library.

    Something that is kind of hackish and formally unsafe but it's sometimes done is that if the context is a simple data that fits the size of a void * (e.g. an integer) and if your environment is not going to have problems with it you can trick the library by passing a fake void * that is just an integer and you convert it back to an integer when being called from the library (this saves the caller from allocating the context and managing its lifetime).

    On how to how to trick the language to avoid this limitation (still remaining in the land of portable C) I can think some hack:

    First we allocate a pool of two-arguments callbacks and context data

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

    then we write (or macro-generate) functions that we wish to register and that will call the two-arguments versions.

    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); }
    

    We also store them in a pool where they're allocated and deallocated:

    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 };
    

    Then to bind the first parameter we can do something like

    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];
    }
    

    but bound functions must also be explicitly deallocated

    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;
    }
    

提交回复
热议问题