2013-09-27 70 views
6

Tôi đang sử dụng std :: bind để cung cấp một cuộc gọi lại trong khi abstracting một số logic bằng cách ràng buộc một số tham số đầu tiên. tức làThi đua std :: bind trong C

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 

} 

Vì vậy, trong ví dụ trên, do_action không cần biết về secret_id và các chức năng khác có thể sử dụng lại mà không có bí mật riêng. Điều này đặc biệt hữu ích khi do_action là một loại hoạt động không đồng bộ.

Câu hỏi của tôi là, có cách nào để ràng buộc các giá trị tham số cho các con trỏ hàm chỉ sử dụng C không?

Nếu không bằng cách mô phỏng std :: bind thì có cách nào khác để truyền dữ liệu từ đầu tiên() đến hàm gọi lại() mà không làm phức tạp do_action() trung lập không?

Trả lời

12

No. C không cho phép bạn thực hiện điều đó trực tiếp.

Trong C cách tiêu chuẩn để xử lý callbacks đang sử dụng con trỏ ngữ cảnh:

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

điều này có nghĩa rằng bạn sẽ vượt qua một chức năng mà sẽ chấp nhận một void * ngoài các thông số bình thường mà gọi lại nên xử lý (trong trường hợp trên là số nguyên) và bạn cũng sẽ chuyển một số void * mà bạn muốn được trả lại.

Điều này void * thường trỏ đến struct sẽ chứa tất cả thông số hoặc dữ liệu bổ sung bạn cần trong gọi lại và sử dụng phương pháp này thư viện không phụ thuộc vào ngữ cảnh này là gì. Nếu callback không cần bất kỳ ngữ cảnh nào, bạn chỉ cần truyền con trỏ NULL là context và bỏ qua tham số đầu tiên khi được gọi từ thư viện.

Thứ gì đó khá giả mạo và không chính thức nhưng đôi khi được thực hiện là nếu ngữ cảnh là dữ liệu đơn giản phù hợp với kích thước của void * (ví dụ: số nguyên) và nếu môi trường của bạn không có vấn đề gì với nó bạn có thể lừa thư viện bằng cách chuyển một số giả void * chỉ là một số nguyên và bạn chuyển đổi nó thành một số nguyên khi được gọi từ thư viện (điều này giúp người gọi không phân bổ ngữ cảnh và quản lý thời gian của nó).

Về cách làm thế nào để đánh lừa các ngôn ngữ để tránh hạn chế này (vẫn còn lại trong đất của C xách tay) Tôi có thể nghĩ rằng một số hack:

Đầu tiên chúng tôi phân bổ một vũng hai đối số callbacks và dữ liệu bối cảnh

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

thì chúng tôi viết (hoặc macro tạo) chức năng mà chúng tôi muốn đăng ký và sẽ gọi phiên bản hai đối số.

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

Chúng tôi cũng lưu trữ chúng trong một hồ bơi, nơi họ đang được phân bổ và 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 }; 

Sau đó để ràng buộc các tham số đầu tiên chúng ta có thể làm điều gì đó như

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

nhưng ràng buộc chức năng cũng phải được giải quyết rõ ràng

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

Câu trả lời ngắn gọn là không.

Điều duy nhất bạn có thể làm là khai báo một hàm khác có tích hợp secret_id. Nếu bạn đang sử dụng C99 hoặc mới hơn, bạn có thể làm cho nó một chức năng nội tuyến để ít nhất là giới hạn các cuộc gọi chức năng trên cao, mặc dù một trình biên dịch mới hơn có thể làm điều đó của chính nó anyway.

Để thẳng thắn, đó là tất cả std :: bind đang làm, vì nó trả về một cấu trúc templated, std :: bind chỉ đơn giản khai báo một hàm functor mới có secret_id được tích hợp vào nó.

4

Như 6502 giải thích, không thể thực hiện điều này trong C di động mà không có một số đối số ngữ cảnh được chuyển tới cuộc gọi lại, ngay cả khi nó không trực tiếp đặt tên secret_id. Tuy nhiên, có các thư viện như Bruno Haible's trampoline cho phép tạo các hàm C với thông tin bổ sung (bao đóng) thông qua các phương tiện không di động. Các thư viện này làm phép thuật của chúng bằng cách gọi các phần mở rộng của assembly hoặc trình biên dịch, nhưng chúng được chuyển đến nhiều nền tảng phổ biến; nếu họ hỗ trợ kiến ​​trúc bạn, chúng sẽ hoạt động tốt.

Taken từ the web, đây là một ví dụ về mã mà tấm bạt lò xo cho phép được này chức năng bậc cao mà có các thông số a, bc (tương tự như secret_id của bạn, và trả về một chức năng chính xác một tham số x rằng tính toán a*x^2 + b*x + c :

#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

Đóng cửa thực sự là cách để đạt được điều này trong "C". LibFFI cung cấp các bao đóng và có thể di chuyển đến hầu hết các hệ điều hành chính: http://sourceware.org/libffi/ –

+0

@BojanNikolic Điểm tốt, và libffi phổ biến hơn nhiều so với tấm bạt lò xo. Một câu trả lời với ví dụ na giải quyết vấn đề của OP bằng cách sử dụng libffi có thể được đánh giá cao. – user4815162342

3

một loại đục và giữ bí mật trong một nguồn nên làm điều đó:

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

(Ở trên không ghi địa chỉ chức năng gọi lại)

Các vấn đề liên quan