Từ 1 tới 8 trên tổng số 8 kết quả

Đề tài: Con trỏ hàm trong C/C++ - Function Pointer

  1. #1
    Ngày gia nhập
    07 2006
    Nơi ở
    Hanoi, Vietnam
    Bài viết
    2,750

    Mặc định Con trỏ hàm trong C/C++ - Function Pointer

    Mục lục

    1. 1 Giới thiệu
      1. 1.1 Function Pointer là gì?
      2. 1.2 Thay thế câu lệnh Switch như thế nào?

    2. 2 Syntax của C và C++ function pointer
      1. 2.1 Define một function pointer
      2. 2.2 Calling Convention
      3. 2.3 Gán một địa chỉ vào function pointer
      4. 2.4 So sánh các function pointer
      5. 2.5 Gọi một hàm sử dụng function pointer
      6. 2.6 Truyền function pointer như là một tham số
      7. 2.7 Trả về một function pointer
      8. 2.8 Sử dụng một mảng các function pointer

    3. 3 Implement Callback Functions in C and C++
      1. 3.1 Khái niệm về callback function
      2. 3.2 Implement a callback function in C
      3. 3.3 Implement a Callback to a static C++ Member Function
      4. 3.4 Implement a Callback to a non-static C++ Member Function



    1 Giới thiệu

    Function Pointer cung cấp một kỹ thuật lập trình cực kỳ thú vị, hiệu quả và “đầy màu sắc”. Chúng ta có thể sử dụng nó để thay thế câu lệnh switch/if, xây dựng quá trình late-binding hoặc implement hàm callback. Tiếc thay, có thể vì sự phức tạp của nó mà nó được đề cập rất ít trong hầu hết sách và tài liệu. Nếu có thì nó chỉ được trình bày một cách rất tóm tắt và sơ sài. Thực ra thì nó ít gây ra lỗi hơn so với pointer bình thường bởi vì chúng ta không bao giờ phải allocate hoặc de-allocate bộ nhớ cả. Tất cả việc chúng ta cần làm là hiểu nó làm gì và học cú pháp của nó. Nhưng hãy luôn tâm niệm rằng: hãy tự hỏi bạn có thực sự cần đến function pointer hay không? Rất tuyệt để thể hiện cách thức late-binding, thế nhưng sử dụng cấu trúc hiện tại của C++ làm cho đoạn mã trở nên dễ đọc và rõ ràng hơn. Một khía cạnh khác của late-binding là runtime: nếu bạn gọi một virtual function, chương trình sẽ xác định hàm nào được gọi. Nó làm điều đó bằng cách sử dụng V-Table mà chứa tất cả những hàm có thể gọi. Điều đó có vẻ hơi lãng phí mỗi lần gọi, và có thể bạn sẽ tiết kiệm một chút nếu sử dụng function pointer thay vì virtual function. Cũng có thể không …
    1.1 Function Pointer là gì?

    Function pointer là một pointer mà nó chỉ đến địa chỉ của một hàm. Bạn phải luôn giữ trong đầu rằng một chương trình chạy sẽ chiếm một không gian bộ nhớ xác định trong bộ nhớ chính. Cả đoạn chương trình thực thi đã được dịch từ mã mà bạn viết và các biến sử dụng đều được đưa vào trong không gian bộ nhớ này. Vì vậy một function trong chương trình của bạn không có gì khác hơn là một địa chỉ trong bộ nhớ.
    1.2 Thay thế câu lệnh Switch như thế nào?

    Khi chúng ta muốn gọi một hàm DoIt() ở một label xác định trong chương trình, chúng ta phải để lời gọi tới hàm DoIt() tại label đó. Sau đó biên dịch và mỗi khi chương trình chạy tới label đó thì hàm DoIt() sẽ được gọi. Mọi thứ đều ok, nhưng sẽ làm gì nếu giả sử chúng ta không biết tại thời điểm build-time (thời gian dịch) hàm nào sẽ được gọi? Nghĩa là chỉ đến lúc chạy ta mới biết ở label đó thì nên chạy DoIt() hay một hàm nào khác. Đó chính là lúc chúng ta muốn sử dụng đến callback-function hoặc là sử dụng kỹ thuật lấy ra từ một “pool” chứa các possible function. Tuy nhiên thì chúng ta có thể giải quyết vấn đề này bằng cách sử dụng lệnh switch, và lựa chọn lời gọi đến hàm thích hợp ở những nhánh khác nhau tùy theo giá trị biểu thức của switch. Nhưng vẫn có một cách khác là sử dụng function pointer. Trong ví dụ sau đây chúng ta thực hiện nhiệm vụ của bốn toán tử toán học cơ bản (+, -, *, /). Cách đầu tiên sử dụng switch và cách thứ hai sử dụng function pointer.
    C++ Code:
    1. //------------------------------------------------------------------------------
    2. // 1.2 Introductory Example or How to Replace a Switch-Statement
    3. // Task: Perform one of the four basic arithmetic operations specified by the
    4. // characters '+', '-', '*' or '/'.
    5. // The four arithmetic operations ... one of these functions is selected
    6. // at runtime with a swicth or a function pointer
    7. float Plus (float a, float b) {
    8.     return a+b;
    9. }
    10. float Minus (float a, float b) {
    11.     return a-b;
    12. }
    13. float Multiply(float a, float b) {
    14.     return a*b;
    15. }
    16. float Divide (float a, float b) {
    17.     return a/b;
    18. }
    19. // Solution with a switch-statement - <opCode> specifies which operation to execute
    20. void Switch(float a, float b, char opCode) {
    21.     float result;
    22.     // execute operation
    23.     switch(opCode) {
    24.         case '+' :
    25.         result = Plus (a, b);
    26.         break;
    27.         case '-' :
    28.         result = Minus (a, b);
    29.         break;
    30.         case '*' :
    31.         result = Multiply (a, b);
    32.         break;
    33.         case '/' :
    34.         result = Divide (a, b);
    35.         break;
    36.     }
    37.     cout << "Switch: 2+5=" << result << endl; // display result
    38. }
    39. // Solution with a function pointer - <pt2Func> is a function pointer and points to
    40. // a function which takes two floats and returns a float. The function pointer
    41. // "specifies" which operation shall be executed.
    42. void Switch_With_Function_Pointer(float a, float b, float (*pt2Func)(float, float)) {
    43.     float result = pt2Func(a, b); // call using function pointer
    44.     cout << "Switch replaced by function pointer: 2-5="; // display result
    45.     cout << result << endl;
    46. }
    47. // Execute example code
    48. void Replace_A_Switch() {
    49.     cout << endl << "Executing function 'Replace_A_Switch'" << endl;
    50.     Switch(2, 5, /* '+' specifies function 'Plus' to be executed */ '+');
    51.     Switch_With_Function_Pointer(2, 5, /* pointer to function 'Minus' */ &Minus);
    52. }

    Chú ý: Một function pointer luôn trỏ đến một function đặc biệt nên tất cả những function mà chúng ta muốn sử dụng với cùng một function pointer thì phải có cùng tham số và giá trị trả về. Nói một cách khác là cùng prototype.
    2 Syntax của C và C++ function pointer

    Dựa vào cú pháp thì có hai loại function pointer khác nhau: một là những function pointer trỏ đến C function hoặc static C++ member function, một là những function pointer tới non-static C++ member function. Sự khác biệt cơ bản là tất cả pointer đến non-static member function cần một tham số ẩn: con trỏ this tới instance của class. Vậy chỉ cần nhớ rằng có hai loại function pointer không tương thích với nhau.
    2.1 Define một function pointer

    Vì function pointer không khác gì hơn một biến nên nó phải được define giống như thông thương. Ví dụ dưới đây chúng ta khai báo các function pointer tên là pt2Function, pt2Member và pt2ConstMember. Chúng trở đến function và lấy một biến float và hai biến char và trả về một số int. Ở ví dụ C++ chúng ta giả sử rằng function mà function pointer trỏ đến là non-static member function của TMyClass.
    C++ Code:
    1. // 2.1 define a function pointer and initialize to NULL
    2. int (*pt2Function)(float, char, char) = NULL; // C
    3. int (TMyClass::*pt2Member)(float, char, char) = NULL; // C++
    4. int (TMyClass::*pt2ConstMember)(float, char, char) const = NULL; // C++

    2.2 Calling Convention

    Thông thường chúng ta không phải nghĩ về calling convention của một function. Trình biên dịch giả định rằng cdecl là convention mặc định nếu chúng ta không sử dụng một convention khác. Nếu bạn muốn tìm hiểu kỹ hơn hãy tìm đọc. Calling convention nói cho trình biên dịch biết cách truyền tham số và cách tạo ra một function. Ví dụ về những calling convention khác là stdcall, pascal, fastcall. Nếu function và function pointer khác calling convention thì chúng cũng không tương thích với nhau và không thể thực hiện phép gán function pointer vào địa chỉ của function kia. Đối với trình biên dịch của Borland và Microsoft thì cần khai báo calling convention ở giữa kiểu trả về và tên hàm hay tên function pointer. Đối với GNU GCC thì sử dụng từ khóa __attribute__: viết khai báo hàm theo sau bởi từ khóa __attribute__ và sau đó là trạng thái của calling convention ở trong1.
    C Code:
    1. // 2.2 define the calling convention
    2. void __cdecl DoIt(float a, char b, char c); // Borland and Microsoft
    3. void DoIt(float a, char b, char c) __attribute__((cdecl)); // GNU GCC

    2.3 Gán một địa chỉ vào function pointer

    Rất dễ dàng để gán một địa chỉ của một function vào function pointer. Đơn giản chỉ cần lấy tên của function hoặc member function thích hợp. Mặc dù hầu hết các compiler support việc đó nhưng tốt hơn hết là chúng ta sử dụng toán tử địa chỉ & và đặt trước các function name để viết những đoạn mã portable. Chúng ta cũng phải sử dụng tên đầy đủ của member function bao gồm tên lớp và toán tử scope (:. Chúng ta cũng phải đảm bảo rằng chúng ta được quyền truy nhập vào function ở bên trong scope đó.
    C Code:
    1. // 2.3 assign an address to the function pointer
    2. // Note: Although you may ommit the address operator on most compilers
    3. // you should always use the correct way in order to write portable code.
    4. // C
    5. int DoIt (float a, char b, char c) {
    6.     printf("DoIt\n");
    7.     return a+b+c;
    8. }
    9. int DoMore(float a, char b, char c)const {
    10.     printf("DoMore\n");
    11.     return a-b+c;
    12. }
    13. pt2Function = DoIt; // short form
    14. pt2Function = &DoMore; // correct assignment using address operator
    C++ Code:
    1. // C++
    2. class TMyClass {
    3. public:
    4.     int DoIt(float a, char b, char c) {
    5.         cout << "TMyClass::DoIt"<< endl;
    6.         return a+b+c;
    7.     };
    8.     int DoMore(float a, char b, char c) const {
    9.         cout << "TMyClass::DoMore" << endl;
    10.         return a-b+c;
    11.     };
    12.     /* more of TMyClass */
    13. };
    14. pt2ConstMember = &TMyClass::DoMore; // correct assignment using address operator
    15. pt2Member = &TMyClass::DoIt; // note: <pt2Member> may also legally point to &DoMore

    2.4 So sánh các function pointer

    Chúng ta có thể sử dụng các toán tử so sánh (==, !=) như bình thường. Trong ví dụ dưới đây nó được kiểm tra xem pt2Function và pt2Member có thực sự chứa địa chỉ của hàm DoIt() và TMyClass:oMore()
    C++ Code:
    1. // 2.4 comparing function pointers
    2. // C
    3. if(pt2Function >0) { // check if initialized
    4.     if(pt2Function == &DoIt)
    5.         printf("Pointer points to DoIt\n");
    6. } else
    7.     printf("Pointer not initialized!!\n");
    8. // C++
    9. if(pt2ConstMember == &TMyClass::DoMore)
    10.     cout << "Pointer points to TMyClass::DoMore" << endl;

    2.5 Gọi một hàm sử dụng function pointer

    Trong C, chúng ta gọi một hàm sử dụng function pointer bằng cách explicitly dereferencing nó bằng toán tử *. Một lựa chọn khác là sử dụng function pointer thay vì function name. Trong C++ hai toán tử .* và ->* được sử dụng cùng với instance của một class để gọi một (non-static) member function. Nếu lời gọi diễn ra bên trong một member function khác, chúng ta có thể sử dụng con trỏ this.
    C Code:
    1. // 2.5 calling a function using a function pointer
    2. int result1 = pt2Function (12, 'a', 'b'); // C short way
    3. int result2 = (*pt2Function) (12, 'a', 'b'); // C
    4. TMyClass instance1;
    5. int result3 = (instance1.*pt2Member)(12, 'a', 'b'); // C++
    6. int result4 = (*this.*pt2Member)(12, 'a', 'b'); // C++ if this-pointer can be used
    7. TMyClass* instance2 = new TMyClass;
    8. int result4 = (instance2->*pt2Member)(12, 'a', 'b'); // C++, instance2 is a pointer
    9. delete instance2;

    2.6 Truyền function pointer như là một tham số

    Chúng ta có thể truyền function pointer như một tham số của một function được gọi khác. Điều đó rất cần thiết nếu chúng ta muốn truyền một con trỏ tới một callback function. Đoạn mã dưới đây chỉ cách truyền một pointer tới một function mà trả về một số nguyên và lấy một số float và 2 char làm tham số:
    C++ Code:
    1. //------------------------------------------------------------------------------------
    2. // 2.6 How to Pass a Function Pointer
    3. // <pt2Func> is a pointer to a function which returns an int and takes a float and two char
    4. void PassPtr(int (*pt2Func)(float, char, char)) {
    5.     int result = (*pt2Func)(12, 'a', 'b'); // call using function pointer
    6.     cout << result << endl;
    7. }
    8. // execute example code - 'DoIt' is a suitable function like defined above in 2.1-4
    9. void Pass_A_Function_Pointer() {
    10.     cout << endl << "Executing 'Pass_A_Function_Pointer'" << endl;
    11.     PassPtr(&DoIt);
    12. }

    2.7 Trả về một function pointer

    Hơi mẹo một chút nhưng một function pointer có thể là giá trị trả về của một function. Trong ví dụ sau đây có hai giải pháp cho việc trả về một function pointer. Nếu muốn trả về một pointer vào một member function, chúng ta phải thay đổi definitions và declarations của tất cả các function pointer.
    C++ Code:
    1. //------------------------------------------------------------------------------------
    2. // 2.7 How to Return a Function Pointer
    3. // 'Plus' and 'Minus' are defined above. They return a float and take two float
    4. // Direct solution: Function takes a char and returns a pointer to a
    5. // function which is taking two floats and returns a float. <opCode>
    6. // specifies which function to return
    7. float (*GetPtr1(const char opCode))(float, float) {
    8.     if(opCode == '+')
    9.         return &Plus;
    10.     else
    11.         return &Minus;
    12. } // default if invalid operator was passed
    13. // Solution using a typedef: Define a pointer to a function which is taking
    14. // two floats and returns a float
    15. typedef float(*pt2Func)(float, float);
    16. // Function takes a char and returns a function pointer which is defined
    17. // with the typedef above. <opCode> specifies which function to return
    18. pt2Func GetPtr2(const char opCode) {
    19.     if(opCode == '+')
    20.         return &Plus;
    21.     else
    22.         return &Minus; // default if invalid operator was passed
    23. }
    24. // Execute example code
    25. void Return_A_Function_Pointer() {
    26.     cout << endl << "Executing 'Return_A_Function_Pointer'" << endl;
    27.     // define a function pointer and initialize it to NULL
    28.     float (*pt2Function)(float, float) = NULL;
    29.     pt2Function=GetPtr1('+'); // get function pointer from function 'GetPtr1'
    30.     cout << (*pt2Function)(2, 4) << endl; // call function using the pointer
    31.     pt2Function=GetPtr2('-'); // get function pointer from function 'GetPtr2'
    32.     cout << (*pt2Function)(2, 4) << endl; // call function using the pointer
    33. }

    2.8 Sử dụng một mảng các function pointer

    Sử dụng một mảng các function pointer khá thú vị. Nó cho phép khả năng lựa chọn một function sử dụng chỉ số (index). Cú pháp thì khá phức tạp và thường xuyên dẫn đến sự nhầm lẫn. Đoạn mã dưới đây bạn sẽ tìm thấy hai cách sử dụng một mảng các function pointer trong C và C++. Cách thứ nhất sử dụng typedef và cách thứ hai sử dụng trức tiếp cách khai bao mảng. Nó tùy thuộc vào bạn thích cách nào hơn.
    C Code:
    1. //------------------------------------------------------------------------------------
    2. // 2.8 How to Use Arrays of Function Pointers
    3. // C ---------------------------------------------------------------------------------
    4. // type-definition: 'pt2Function' now can be used as type
    5. typedef int (*pt2Function)(float, char, char);
    6. // illustrate how to work with an array of function pointers
    7. void Array_Of_Function_Pointers() {
    8.     printf("\nExecuting 'Array_Of_Function_Pointers'\n");
    9.     // define arrays and ini each element to NULL, <funcArr1> and <funcArr2> are arrays
    10.     // with 10 pointers to functions which return an int and take a float and two char
    11.     // first way using the typedef
    12.     pt2Function funcArr1[10] = {
    13.                                    NULL
    14.                                };
    15.     // 2nd way directly defining the array
    16.     int (*funcArr2[10])(float, char, char) = {
    17.                 NULL
    18.             };
    19.     // assign the function's address - 'DoIt' and 'DoMore' are suitable functions
    20.     // like defined above in 2.1-4
    21.     funcArr1[0] = funcArr2[1] = &DoIt;
    22.     funcArr1[1] = funcArr2[0] = &DoMore;
    23.     /* more assignments */
    24.     // calling a function using an index to address the function pointer
    25.     printf("%d\n", funcArr1[1](12, 'a', 'b')); // short form
    26.     printf("%d\n", (*funcArr1[0])(12, 'a', 'b')); // "correct" way of calling
    27.     printf("%d\n", (*funcArr2[1])(56, 'a', 'b'));
    28.     printf("%d\n", (*funcArr2[0])(34, 'a', 'b'));
    29. }

    C++ Code:
    1. // C++-------------------------------------------------------------------------------
    2. // type-definition: 'pt2Member' now can be used as type
    3. typedef int (TMyClass::*pt2Member)(float, char, char);
    4. // illustrate how to work with an array of member function pointers
    5. void Array_Of_Member_Function_Pointers() {
    6.     cout << endl << "Executing 'Array_Of_Member_Function_Pointers'" << endl;
    7.     // define arrays and ini each element to NULL, <funcArr1> and <funcArr2> are
    8.     // arrays with 10 pointers to member functions which return an int and take
    9.     // a float and two char
    10.     // first way using the typedef
    11.     pt2Member funcArr1[10] = {
    12.                                  NULL
    13.                              };
    14.     // 2nd way of directly defining the array
    15.     int (TMyClass::*funcArr2[10])(float, char, char) = {
    16.                 NULL
    17.             };
    18.     // assign the function's address - 'DoIt' and 'DoMore' are suitable member
    19.     // functions of class TMyClass like defined above in 2.1-4
    20.     funcArr1[0] = funcArr2[1] = &TMyClass::DoIt;
    21.     funcArr1[1] = funcArr2[0] = &TMyClass::DoMore;
    22.     /* more assignments */
    23.     // calling a function using an index to address the member function pointer
    24.     // note: an instance of TMyClass is needed to call the member functions
    25.     TMyClass instance;
    26.     cout << (instance.*funcArr1[1])(12, 'a', 'b') << endl;
    27.     cout << (instance.*funcArr1[0])(12, 'a', 'b') << endl;
    28.     cout << (instance.*funcArr2[1])(34, 'a', 'b') << endl;
    29.     cout << (instance.*funcArr2[0])(89, 'a', 'b') << endl;
    30. }

    3 Implement Callback Functions in C and C++

    3.1 Khái niệm về callback function

    Function pointer cung cấp khái niệm về callback function. Trong bài viết này sẽ giới thiệu về callback function thông qua một hàm về giải thuật sắp xếp nổi tiếng là qsort. Hàm này sẽ sắp xếp các phần tử theo một tiêu chuẩn sắp xếp mà user định nghĩa. Kích thước của một item và số item cần sắp xếp sẽ được truyền vào hàm này. Câu hỏi đặt ra là: làm thế nào để hàm qsort có thể sắp xếp các phần tử mà không có một thông tin gì về kiểu của phần tử sắp xếp? Câu trả lời rất đơn giản: hàm qsort sẽ nhận một function pointer tới một hàm so sánh như một tham số. Hàm so sánh sẽ nhận hai tham số kiểu con trỏ void tới hai phần tử và sẽ đánh giá thứ tự của hai phần tử đó và trả về một số int. Do đó mỗi lần giải thuật sắp xếp cần đánh giá ví trí của hai phần tử thì đơn giản chỉ là gọi tới hàm so sánh thông qua function pointer.
    3.2 Implement a callback function in C

    Hãy tham khảo khai báo của hàm qsort dưới đây:
    void qsort(void* field, size_t nElements, size_t sizeOfAnElement,
    int(_USERENTRY *cmpFunc)(const void *, const void*));

    field trỏ tới phần tử đầu tiên của mảng cần sắp xếp, nElements là số phần tử cần sắp xếp, sizeOfAnElement là kích thước của một phần tử tính theo byte, và cmpFunc là function pointer tới hàm so sánh. Hàm so sánh này lấy hai phần tử const void* và trả về một số nguyên.
    C Code:
    1. void qsort( ... , int(_USERENTRY *cmpFunc)(const void*, const void*))
    2. {
    3.     /* sort algorithm - note: item1 and item2 are void-pointers */
    4.     int bigger=cmpFunc(item1, item2); // make callback
    5.     /* use the result */
    6. }

    Ví dụ mẫu sử dụng qsort
    C Code:
    1. //-----------------------------------------------------------------------------------------
    2. // 3.3 How to make a callback in C by the means of the sort function qsort
    3. #include <stdlib.h> // due to: qsort
    4. #include <time.h> // randomize
    5. #include <stdio.h> // printf
    6. // comparison-function for the sort-algorithm
    7. // two items are taken by void-pointer, converted and compared
    8. int CmpFunc(const void* _a, const void* _b) {
    9.     // you've got to explicitly cast to the correct type
    10.     const float* a = (const float*) _a;
    11.     const float* b = (const float*) _b;
    12.     if(*a > *b)
    13.         return 1; // first item is bigger than the second one -> return 1
    14.     else
    15.         if(*a == *b)
    16.             return 0; // equality -> return 0
    17.         else
    18.             return -1; // second item is bigger than the first one -> return -1
    19. }
    20. // example for the use of qsort()
    21. void QSortExample() {
    22.     float field[100];
    23.     ::randomize(); // initialize random-number-generator
    24.     for(int c=0;c<100;c++) // randomize all elements of the field
    25.         field[c]=random(99);
    26.     // sort using qsort()
    27.     qsort((void*) field, /*number of items*/ 100, /*size of an item*/ sizeof(field[0]),
    28.           /*comparison-function*/ CmpFunc);
    29.     // display first ten elements of the sorted field
    30.     printf("The first ten elements of the sorted field are ...\n");
    31.     for(int c=0;c<10;c++)
    32.         printf("element #%d contains %.0f\n", c+1, field[c]);
    33.     printf("\n");
    34. }

    3.3 Implement a Callback to a static C++ Member Function

    Implement callback tới static C++ member function cũng giống như implement callback tới hàm C. Static member function không cần một object để thực thi vì vậy nó phải có cùng một prototype như là hàm C với cùng một calling convention, tham số và giá trị trả về.
    3.4 Implement a Callback to a non-static C++ Member Function

    Function pointer tới non-static member thì khác với là một con trỏ hàm C vì nó cần phải truyền một con trỏ this của object. Nếu bạn chỉ muốn gọi hàm callback tới một member function của một class cụ thể thì chỉ cần chuyển đoạn mã từ một function pointer bình thường tới con trỏ tới member function. Nhưng sẽ làm thế nào nếu muốn gọi hàm callback tới một non-static member của một class chưa rõ nào đó (có thể ở các class khác nhau). Nó khó hơn một chút. Chúng ta cần phải viết một static member function như là một wrapper. Static member function thì được coi như là một function bình thường. Sau đó thì chúng ta sẽ ép kiểu con trỏ tới object mà chúng ta muốn nó thực hiện member function, thành void* và truyền nó tới wrapper như là một tham số phụ thêm hoặc là thông qua một biến toàn cục. Tất nhiên chúng ta cũng truyền các tham số gọi tới member function. Wrapper sẽ ép kiểu con trỏ void thành con trỏ tới object của class tương ứng của nó và gọi member function (kỹ thuật này giống với kỹ thuật chúng ta xây dựng class Thread). Dưới đây là hai ví dụ:
    Ví dụ A: Con trỏ tới object được truyền như là một tham số phụ thêm: Hàm DoItA() thực hiện với object của class TClassA để gọi hàm callback. Vì vậy con trỏ tới object của class TClassA và con trỏ tới static wrapper function TClassA::Wrapper_To_Call_Display được truyền tới DoItA(). Hàm wrapper này là callback function. Chúng ta có thể viết những class khác giống như TClassA và sử dụng chung với DoItA miễn là những class này cũng cung cấp các function cần thiết. Chú ý là giải pháp này có thể hữu dụng nếu chúng ta tự thiết kế giao diện của hàm callback. Nó tốt hơn rất nhiều so với giải pháp thứ hai sử dụng biến toàn cục.
    C++ Code:
    1. //-----------------------------------------------------------------------------------------
    2. // 3.5 Example A: Callback to member function using an additional argument
    3. // Task: The function 'DoItA' makes something which implies a callback to
    4. // the member function 'Display'. Therefore the wrapper function
    5. // 'Wrapper_To_Call_Display is used.
    6. #include <iostream.h> // due to: cout
    7. class TClassA {
    8. public:
    9.     void Display(const char* text) {
    10.         cout << text << endl;
    11.     };
    12.     static void Wrapper_To_Call_Display(void* pt2Object, char* text);
    13.     /* more of TClassA */
    14. };
    15. // static wrapper function to be able to callback the member function Display()
    16. void TClassA::Wrapper_To_Call_Display(void* pt2Object, char* string) {
    17.     // explicitly cast to a pointer to TClassA
    18.     TClassA* mySelf = (TClassA*) pt2Object;
    19.     // call member
    20.     mySelf->Display(string);
    21. }
    22. // function does something which implies a callback
    23. // note: of course this function can also be a member function
    24. void DoItA(void* pt2Object, void (*pt2Function)(void* pt2Object, char* text)) {
    25.     /* do something */
    26.     pt2Function(pt2Object, "hi, i'm calling back using a argument ;-)"); // make callback
    27. }
    28. // execute example code
    29. void Callback_Using_Argument() {
    30.     // 1. instantiate object of TClassA
    31.     TClassA objA;
    32.     // 2. call 'DoItA' for <objA>
    33.     DoItA((void*) &objA, TClassA::Wrapper_To_Call_Display);
    34. }

    Ví dụ B: Con trỏ tới object được lưu trữ trong một biến toàn cục. Hàm DoItB sẽ thực hiện với đối tượng của class B để gọi hàm callback. Một con trỏ tới static wrapper function TClassB::Wrapper_To_Call_Display được truyền vào DoItB. Hàm wrapper này là callback function. Wrapper sử dụng biến toàn cục void* pt2Object và phép ép kiểu thành một đối tượng của TClassB. Phải luôn chú ý khởi tạo biến toàn cục để chỉ đến đối tượng của class chính xác. Chúng ta có thể viết những class khác như TClassB và sử dụng chúng với DoItB miễn là những class đó cung cấp các function cần thiết. Cách này không phải là một giải pháp tốt bởi vì sử dụng biến toàn cục rất nguy hiểm và có thể gây ra những lỗi nghiêm trọng.
    C++ Code:
    1. //------------------------------------------------------------------------------
    2. // 3.5 Example B: Callback to member function using a global variable
    3. // Task: The function 'DoItB' makes something which implies a callback to
    4. // the member function 'Display'. Therefore the wrapper function
    5. // 'Wrapper_To_Call_Display is used.
    6. #include <iostream.h> // due to: cout
    7. void* pt2Object; // global variable which points to an arbitrary object
    8. class TClassB {
    9. public:
    10.     void Display(const char* text) {
    11.         cout << text << endl;
    12.     };
    13.     static void Wrapper_To_Call_Display(char* text);
    14.     /* more of TClassB */
    15. };
    16. // static wrapper function to be able to callback the member function Display()
    17. void TClassB::Wrapper_To_Call_Display(char* string) {
    18.     // explicitly cast global variable <pt2Object> to a pointer to TClassB
    19.     // warning: <pt2Object> MUST point to an appropriate object!
    20.     TClassB* mySelf = (TClassB*) pt2Object;
    21.     // call member
    22.     mySelf->Display(string);
    23. }
    24. // function does something which implies a callback
    25. // note: of course this function can also be a member function
    26. void DoItB(void (*pt2Function)(char* text)) {
    27.     /* do something */
    28.     pt2Function("hi, i'm calling back using a global ;-)"); // make callback
    29. }
    30. // execute example code
    31. void Callback_Using_Global() {
    32.     // 1. instantiate object of TClassB
    33.     TClassB objB;
    34.     // 2. assign global variable which is used in the static wrapper function
    35.     // important: never forget to do this!!
    36.     pt2Object = (void*) &objB;
    37.     // 3. call 'DoItB' for <objB>
    38.     DoItB(TClassB::Wrapper_To_Call_Display);
    39. }

    Bài viết này được Hoang Tran(openandfree.org) dịch từ hxxp://www.newty.de/fpt/index.html
    Email: admin[@]congdongcviet.com | CC to: info[@]congdongcviet.com
    Phone: 0972 89 7667 (Office: 04 6329 2380)
    Yahoo & Skype: dreaminess_world (Vui lòng chỉ rõ mục đích ngay khi liên hệ, cảm ơn!)

    Một người nào đó coi thường ý thức kỷ luật cũng có nghĩa là người đó đã coi thường tương lai số phận của chính bản thân người đó. Những người coi thường ý thức kỷ luật sẽ không bao giờ có được sự thành công trong sự nghiệp!

  2. #2
    Ngày gia nhập
    08 2011
    Bài viết
    1

    Em đọc VD trên mà không hiểu rõ tác dụng của cái function pointer này lắm, anh giải thích giúp em.Theo VD như trên chương trình của mình có 4 phép tính "+,-,*,/" và mình cho người dùng chọn phép tính rồi dùng switch-case để chọn phép tính tương ứng(không sử dụng function pointer).Bây giờ nếu dử dụng function pointer thì mình cho con trỏ trỏ tới hàm cần thực thi, nhưng làm sao biết nên trỏ tới hàm nào bây giờ, chẳng lẽ lại vẫn sử dụng switch-case để biết cần trỏ tới hàm nào hay sao, vậy nó có gì tiện hơn so với sử dụng switch-case đâu(em thấy trong VD trỏ thẳng luôn tới hàm Minus mà không cho người dùng chọn nữa).Em mới học đọc sách tới chỗ này thấy khó hiểu quá.

  3. #3
    Ngày gia nhập
    01 2011
    Nơi ở
    \WINDOWS\Assembly\
    Bài viết
    54

    Cám ơn kevin rất nhiều
    mình đang nghiên cứu về con trong trong c/c++

  4. #4
    Ngày gia nhập
    10 2008
    Bài viết
    5

    Hi bác Kevin,
    Hiện nay em đang gặp vấn đề về callback khi sử dụng callback function giữa các layer khác nhau:
    Em muốn hỏi bác làm sao để truyền tham số khi 2 function pointer khác kiểu nhau ví dụ như sau
    Code:
    typedef struct {
        INT32 (*seek) (INT32 fd, UINT64 offset, INT32 whence, UINT64 *ret_offset);
        ....
    } CF_MGR1
    
    typedef struct {
        INT32 (*seek) (INT32 fd, UINT32 offset, INT32 whence, UINT32 *ret_offset);
        ....
    } CF_MGR2
    
    INT32 fseek1(INT32 fd, UINT64 offset, INT32 whence, UINT64 *ret_offset);
    {
    ...
     return E_OK;
    }
    INT32 fseek2(INT32 fd, UINT32 offset, INT32 whence, UINT32 *ret_offset);
    {
    ...
    return E_OK
    }
    void main()
    {
        CF_MGR1 cf1;
        CF_MGR2 cf2;
        //Có thể gán theo các kiểu sau được không:
        //Nếu được thì có thì làm để không có warning: assignment from incompatible pointer type
        cf1.seek = fseek2;
        cf2.seek = fseek1;
        ...
    }
    Thanks các bác ạ!
    Đã được chỉnh sửa lần cuối bởi quanganh12 : 12-01-2012 lúc 09:47 AM.

  5. #5
    Ngày gia nhập
    07 2006
    Nơi ở
    Hanoi, Vietnam
    Bài viết
    2,750

    Bạn có thể sử dụng default parameters!
    Email: admin[@]congdongcviet.com | CC to: info[@]congdongcviet.com
    Phone: 0972 89 7667 (Office: 04 6329 2380)
    Yahoo & Skype: dreaminess_world (Vui lòng chỉ rõ mục đích ngay khi liên hệ, cảm ơn!)

    Một người nào đó coi thường ý thức kỷ luật cũng có nghĩa là người đó đã coi thường tương lai số phận của chính bản thân người đó. Những người coi thường ý thức kỷ luật sẽ không bao giờ có được sự thành công trong sự nghiệp!

  6. #6
    Ngày gia nhập
    10 2008
    Bài viết
    5

    Mặc định Con trỏ hàm trong C/C++ - Function Pointer

    Bác có thể giải thích rõ hơn được không?
    Vì các giá trị output của em đều thông qua con trỏ, trong khi giá trị trả về của hàm chỉ là mã lỗi. Em chưa rõ việc sử dụng default parameters trong trường hợp này ra sao?
    P/S. Em đang viết wrapper cho 1 lớp ở dưới dùng callback ở lớp trên.
    Ở lớp trên dùng hàm seek cho kiểu dữ liệu UINT32 trong khi lớp dưới khai báo là UINT64.
    Cả 2 lớp này em đều không can thiệp đựoc. Phần wrapper sẽ phải là translator giữa 2 lớp này.

  7. #7
    Ngày gia nhập
    07 2006
    Nơi ở
    Hanoi, Vietnam
    Bài viết
    2,750

    1. Chỉ sử dụng UNIT64 thôi, tại sao phải chia như thế?
    2. Hoàn toàn có thể tạo nhiều hàm callback khác nhau cùng 1 tác dụng mà?
    Email: admin[@]congdongcviet.com | CC to: info[@]congdongcviet.com
    Phone: 0972 89 7667 (Office: 04 6329 2380)
    Yahoo & Skype: dreaminess_world (Vui lòng chỉ rõ mục đích ngay khi liên hệ, cảm ơn!)

    Một người nào đó coi thường ý thức kỷ luật cũng có nghĩa là người đó đã coi thường tương lai số phận của chính bản thân người đó. Những người coi thường ý thức kỷ luật sẽ không bao giờ có được sự thành công trong sự nghiệp!

  8. #8
    Ngày gia nhập
    04 2012
    Bài viết
    1

    mấy anh(chị) ơi cho e hỏi xíu nha..công dụng của hàm trả về con trỏ là như thế nào?

Các đề tài tương tự

  1. Viết function để test một function khác như thế nào?
    Gửi bởi c_is_master trong diễn đàn Thắc mắc lập trình C/C++/C++0x
    Trả lời: 2
    Bài viết cuối: 05-05-2012, 02:55 AM
  2. Truyền array pointer 2 chiều vào function ?
    Gửi bởi vietwow trong diễn đàn Thắc mắc lập trình Visual C++
    Trả lời: 2
    Bài viết cuối: 13-04-2011, 10:12 PM
  3. Lập trình C Bài toán nhân 2 vector với pointer trong C
    Gửi bởi leanh2 trong diễn đàn Thắc mắc lập trình C/C++/C++0x
    Trả lời: 1
    Bài viết cuối: 11-12-2010, 11:27 PM
  4. Cách dùng function pointer trong hàm hook?
    Gửi bởi Kỳ Nam trong diễn đàn Thắc mắc lập trình Visual C++
    Trả lời: 5
    Bài viết cuối: 13-07-2009, 03:32 PM
  5. Function Pointer trong Class, lỗi cannot convert from 'int (__cdecl *)(int)' to 'int (__thiscall Class::*)(int)'
    Gửi bởi DKhanh trong diễn đàn Thắc mắc lập trình C/C++/C++0x
    Trả lời: 5
    Bài viết cuối: 15-12-2007, 04:04 PM

Tags của đề tài này

Quyền hạn của bạn

  • Bạn không thể gửi đề tài mới
  • Bạn không thể gửi bài trả lời
  • Bạn không thể gửi các đính kèm
  • Bạn không thể chỉnh sửa bài viết của bạn