What are the advantages of functions pointers over a switch case full of function calls?

Viewed 156

Main use of function pointers (from what I gather) is to pass them along with some variables to a function which will then call the function the pointer points to. To achieve similar results one can pass an arbitrary integer instead of a function pointer and let a switch case call the appropriate function; which will also bypass the restriction that is innate to function pointers, that a function you are passing the pointer to, needs to know exactly what kind of function is coming to it (what it returns and what variables it expects.)

Is there an advantage to using function pointers over the switch case method proposed above? I'm interested in technical capabilities of function pointers that I might be missing and preferably some examples too.

3 Answers

If the functions each have different signatures, you'll need to have a separate case for each one in order to call the right parameters and get the right return type.

If the signatures are the same, you could instead create an array of function pointers and just index the array. This avoids branches and is simpler to maintain. For example:

int add(int, int);
int sub(int, int);

typedef int (*math_func)(int, int);

math_func arr[] = { add, sub };

int call_math_func(int idx, int a, int b)
{
    return math_func[idx](a, b);
}

I prefer using function pointers when possible because imho they make intents more explicit at the calling site, e.g.:

#include <stdio.h>

typedef void (*HelloWorld)(void);

void english(void) { printf("Hello, World!\n"); }
void italian(void) { printf("Ciao, Mondo!\n"); }

void greet_world_in(HelloWorld hello_world) { hello_world(); }

int main(void)
{
    greet_world_in(english);
    greet_world_in(italian);

    return 0;
}

Assuming that the function pointers have exactly the same kind of prototype, a switch case statement is often implemented as a jump table:

void conceptual_dispatcher_emulating_switch_case(int n) {
   static const jmp_ptr_t jmp_table[5]={label_0,label_1,label_2,label_3,label_4};
  
   if (n < 5) {
      goto jmp_table[n];
   }
   return;

   label_0 :  return myfunc_1();
   label_1 :  return myfunc_2();
   label_2 :  return myfunc_3();
   label_3 :  return myfunc_4();
   label_4 :  return myfunc_5();
}

From this it's pretty easy to make an optimization:

void conceptual_dispatcher_better(int n) {
   static const function_ptr_t jmp_table[5]={myfunc_1, myfunc_2, myfunc_3, myfunc_4, myfunc_5};
  
   if (n < 5) {
      goto jmp_table[n];
   }
   return;
}

And from this the next logical step is to

void conceptual_dispatcher_even_better(function_ptr_t *ptr) {
   ptr();
}

And from this the next step is to make that inline or just call the ptr without the dispatcher.

Related