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.
Mã nguồn PHP:
//------------------------------------------------------------------------------// 1.2 Introductory Example or How to Replace a Switch-Statement// Task: Perform one of the four basic arithmetic operations specified by the// characters '+', '-', '*' or '/'.// The four arithmetic operations ... one of these functions is selected// at runtime with a swicth or a function pointerfloat Plus (float a, float b) { return a+b;}float Minus (float a, float b) { return a-b;}float Multiply(float a, float b) { return a*b;}float Divide (float a, float b) { return a/b;}// Solution with a switch-statement - <opCode> specifies which operation to executevoid Switch(float a, float b, char opCode) { float result; // execute operation switch(opCode) { case '+' : result = Plus (a, b); break; case '-' : result = Minus (a, b); break; case '*' : result = Multiply (a, b); break; case '/' : result = Divide (a, b); break; } cout << "Switch: 2+5=" << result << endl; // display result}// Solution with a function pointer - <pt2Func> is a function pointer and points to// a function which takes two floats and returns a float. The function pointer// "specifies" which operation shall be executed.void Switch_With_Function_Pointer(float a, float b, float (*pt2Func)(float, float)) { float result = pt2Func(a, b); // call using function pointer cout << "Switch replaced by function pointer: 2-5="; // display result cout << result << endl;}// Execute example codevoid Replace_A_Switch() { cout << endl << "Executing function 'Replace_A_Switch'" << endl; Switch(2, 5, /* '+' specifies function 'Plus' to be executed */ '+'); Switch_With_Function_Pointer(2, 5, /* pointer to function 'Minus' */ &Minus);}  
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.
Mã nguồn PHP:
// 2.1 define a function pointer and initialize to NULLint (*pt2Function)(float, char, char) = NULL; // Cint (TMy"DoIt
"); return a+b+c;}int DoMore(float a, char b, char c)const { printf("DoMore
"); return a-b+c;}pt2Function = DoIt; // short formpt2Function = &DoMore; // correct assignment using address operator// C++class TMyClass {public: int DoIt(float a, char b, char c) { cout << "TMy"<< endl; return a+b+c; }; int DoMore(float a, char b, char c) const { cout << "TMy" << endl; return a-b+c; }; /* more of TMyClass */};pt2ConstMember = &TMy"Pointer points to DoIt
");} else printf("Pointer not initialized!!
");// C++if(pt2ConstMember == &TMy"Pointer points to TMy" << 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.
Mã nguồn PHP:
// 2.5 calling a function using a function pointerint result1 = pt2Function (12, 'a', 'b'); // C short wayint result2 = (*pt2Function) (12, 'a', 'b'); // CTMyClass instance1;int result3 = (instance1.*pt2Member)(12, 'a', 'b'); // C++int result4 = (*this.*pt2Member)(12, 'a', 'b'); // C++ if this-pointer can be usedTMyClass* instance2 = new TMyClass;int result4 = (instance2->*pt2Member)(12, 'a', 'b'); // C++, instance2 is a pointerdelete 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ố:
Mã nguồn PHP:
//------------------------------------------------------------------------------------// 2.6 How to Pass a Function Pointer// <pt2Func> is a pointer to a function which returns an int and takes a float and two charvoid PassPtr(int (*pt2Func)(float, char, char)) { int result = (*pt2Func)(12, 'a', 'b'); // call using function pointer cout << result << endl;}// execute example code - 'DoIt' is a suitable function like defined above in 2.1-4void Pass_A_Function_Pointer() { cout << endl << "Executing 'Pass_A_Function_Pointer'" << endl; PassPtr(&DoIt);}  
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.

Mã nguồn PHP:
//------------------------------------------------------------------------------------// 2.7 How to Return a Function Pointer// 'Plus' and 'Minus' are defined above. They return a float and take two float// Direct solution: Function takes a char and returns a pointer to a// function which is taking two floats and returns a float. <opCode>// specifies which function to returnfloat (*GetPtr1(const char opCode))(float, float) { if(opCode == '+') return &Plus; else return &Minus;} // default if invalid operator was passed// Solution using a typedef: Define a pointer to a function which is taking// two floats and returns a floattypedef float(*pt2Func)(float, float);// Function takes a char and returns a function pointer which is defined// with the typedef above. <opCode> specifies which function to returnpt2Func GetPtr2(const char opCode) { if(opCode == '+') return &Plus; else return &Minus; // default if invalid operator was passed}// Execute example codevoid Return_A_Function_Pointer() { cout << endl << "Executing 'Return_A_Function_Pointer'" << endl; // define a function pointer and initialize it to NULL float (*pt2Function)(float, float) = NULL; pt2Function=GetPtr1('+'); // get function pointer from function 'GetPtr1' cout << (*pt2Function)(2, 4) << endl; // call function using the pointer pt2Function=GetPtr2('-'); // get function pointer from function 'GetPtr2' cout << (*pt2Function)(2, 4) << endl; // call function using the pointer}  
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.

Mã nguồn PHP:
//------------------------------------------------------------------------------------// 2.8 How to Use Arrays of Function Pointers// C ---------------------------------------------------------------------------------// type-definition: 'pt2Function' now can be used as typetypedef int (*pt2Function)(float, char, char);// illustrate how to work with an array of function pointersvoid Array_Of_Function_Pointers() { printf("
Executing 'Array_Of_Function_Pointers'
"); // define arrays and ini each element to NULL, <funcArr1> and <funcArr2> are arrays // with 10 pointers to functions which return an int and take a float and two char // first way using the typedef pt2Function funcArr1[10] = { NULL }; // 2nd way directly defining the array int (*funcArr2[10])(float, char, char) = { NULL }; // assign the function's address - 'DoIt' and 'DoMore' are suitable functions // like defined above in 2.1-4 funcArr1[0] = funcArr2[1] = &DoIt; funcArr1[1] = funcArr2[0] = &DoMore; /* more assignments */ // calling a function using an index to address the function pointer printf("
%d
", funcArr1[1](12, 'a', 'b')); // short form printf("%d
", (*funcArr1[0])(12, 'a', 'b')); // "correct" way of calling printf("%d
", (*funcArr2[1])(56, 'a', 'b')); printf("%d
", (*funcArr2[0])(34, 'a', 'b'));}  
// C++-------------------------------------------------------------------------------
/
Mã nguồn PHP:
/ type-definition: 'pt2Member' now can be used as typetypedef int (TMy"Executing 'Array_Of_Member_Function_Pointers'" << endl; // define arrays and ini each element to NULL, <funcArr1> and <funcArr2> are // arrays with 10 pointers to member functions which return an int and take // a float and two char // first way using the typedef pt2Member funcArr1[10] = { NULL }; // 2nd way of directly defining the array int (TMy"The first ten elements of the sorted field are ...
"); for(int c=0;c<10;c++) printf("element #%d contains %.0f
", c+1, field[c]); printf("
");}  
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.
Mã nguồn PHP:
//-----------------------------------------------------------------------------------------// 3.5 Example A: Callback to member function using an additional argument// Task: The function 'DoItA' makes something which implies a callback to// the member function 'Display'. Therefore the wrapper function// 'Wrapper_To_Call_Display is used.#include <iostream.h> // due to: coutclass TClassA {public: void Display(const char* text) { cout << text << endl; }; static void Wrapper_To_Call_Display(void* pt2Object, char* text); /* more of TClassA */};// static wrapper function to be able to callback the member function Display()void TClassA::Wrapper_To_Call_Display(void* pt2Object, char* string) { // explicitly cast to a pointer to TClassA TClassA* mySelf = (TClassA*) pt2Object; // call member mySelf->Display(string);}// function does something which implies a callback// note: of course this function can also be a member functionvoid DoItA(void* pt2Object, void (*pt2Function)(void* pt2Object, char* text)) { /* do something */ pt2Function(pt2Object, "hi, i'm calling back using a argument ;-)"); // make callback}// execute example codevoid Callback_Using_Argument() { // 1. instantiate object of TClassA TClassA objA; // 2. call 'DoItA' for <objA> DoItA((void*) &objA, TClassA::Wrapper_To_Call_Display);}  
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.
Mã nguồn PHP:
//------------------------------------------------------------------------------// 3.5 Example B: Callback to member function using a global variable// Task: The function 'DoItB' makes something which implies a callback to// the member function 'Display'. Therefore the wrapper function// 'Wrapper_To_Call_Display is used.#include <iostream.h> // due to: coutvoid* pt2Object; // global variable which points to an arbitrary objectclass TClassB {public: void Display(const char* text) { cout << text << endl; }; static void Wrapper_To_Call_Display(char* text); /* more of TClassB */};// static wrapper function to be able to callback the member function Display()void TClassB::Wrapper_To_Call_Display(char* string) { // explicitly cast global variable <pt2Object> to a pointer to TClassB // warning: <pt2Object> MUST point to an appropriate object! TClassB* mySelf = (TClassB*) pt2Object; // call member mySelf->Display(string);}// function does something which implies a callback// note: of course this function can also be a member functionvoid DoItB(void (*pt2Function)(char* text)) { /* do something */ pt2Function("hi, i'm calling back using a global ;-)"); // make callback}// execute example codevoid Callback_Using_Global() { // 1. instantiate object of TClassB TClassB objB; // 2. assign global variable which is used in the static wrapper function // important: never forget to do this!! pt2Object = (void*) &objB; // 3. call 'DoItB' for <objB> DoItB(TClassB::Wrapper_To_Call_Display);}