Ask the C++ Pro 10-Minute Solution

C、C++ 专栏收录该内容
8 篇文章 0 订阅
Ask the C++ Pro 10-Minute Solution

Declaring Function Pointers and Implementing Callbacks
By Danny Kalev

Programmers often need to implement callbacks. I will discuss the fundamentals of function pointers and show how to use them to implement callbacks. Notice that this article focuses on ordinary functions, not class member functions, which rely on substantially different syntactic and semantic rules (pointers to class members were discussed in a previous 10-Minute Solution).

Declaring a Function Pointer
A callback function is one that is not invoked explicitly by the programmer; rather the responsibility for its invocation is delegated to another function that receives the callback function's address. To implement a callback, you need to define an appropriate function pointer first. Although the syntax is a bit arcane, if you're familiar with function declarations in general, you will notice that a function pointer declaration is very similar to a function declaration. Consider the following example:

   void f(); // a function prototype

It declares a function f() that takes no arguments and returns void. A pointer to such a function has the following type:

  void (*) ();

Let's parse it. The asterisk in the leftmost parentheses is the nucleus of a function pointer declaration. Two additional elements are the function's return type, which appears on the left and is 'void' in our example, and a parameter list enclosed in the rightmost parentheses. In our case, the parameter list is empty because f() takes no arguments. Note that we didn't create a pointer variable yet—we only declared the type of such a variable. We can use this type to create a typedef name, or in a sizeof expression:

  // get the size of a function pointer
  unsigned psize = sizeof (void (*) ()); 

// declare a typedef for a function pointer typedef void (*pfv) ();

pfv is a synonym for "a pointer to a function that takes no arguments and returns void". We can use this typedef name to hide the cumbersome syntax of function pointers.

A pointer variable, of course, has a name. Here is an example of such a pointer:

  void (*p) (); // p is a pointer to a function 

p is a pointer to a function that takes no arguments and returns void. The name of a pointer variable appears on the right of the asterisk, inside the parentheses. We can now assign a value to p. A value is simply a name of a function that has a matching signature (parameter list) and return type. For example:

  void func() 
    /* do something */
  p = func; 

You can assign a different value to p as long as it's the address of a function with the same signature and return type. A function's name is not a part of its type, though.

Passing an Address of a Callback Function to Its Caller
Now we can pass p to another function, caller(), which will call the function to which p points (the callee) without knowing its name:

    void caller(void(*ptr)())
    ptr(); /* call the function to which ptr points */ 
  void func();
  int main()
    p = func; 
    caller(p); /* pass address of func to caller */

If you assign a different function to p, caller() will invoke that function. The assignment can take place at runtime, which enables you to implement dynamic binding.

Calling Conventions
Up until now, we've discussed function pointers and callbacks without discussing compiler-specific conventions that aren't defined by ANSI C/C++. Many compilers have several calling conventions. For example, in Visual C++ you can precede __cdecl, __stdcall or __pascal to a function's type to indicate its calling convention (__cdecl is the default). C++ Builder also supports the __fastcall calling convention. The calling convention affects the compiler-generated name of a given function (i.e., name mangling), the order in which arguments are passed (right to left or left to right), stack cleanup responsibility (by the caller or the callee), and the mechanism for argument passing (stack, CPU registers, etc.).

It's important to note that the calling convention is an integral part of a function's type; you can't assign an address of a function to a pointer with an incompatible calling convention. For example:

  // callee is a function that takes int and returns int
  __stdcall int callee(int);  

  // caller is a function that takes a function pointer
  void caller( __cdecl int(*ptr)(int));  

  // illegal attempt to store the address of callee in p
  __cdecl int(*p)(int) = callee; // error

p and callee() have incompatible types because they have different calling conventions. Therefore, you can't assign callee's address to the pointer p, although both have the same return value and parameter list.

  • 0
  • 0
  • 0
  • 一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
钱包余额 0