Skip to content

Instantly share code, notes, and snippets.

@satyx
Created February 2, 2022 21:01
Show Gist options
  • Save satyx/aaba695a760bd6f066ffc9177b11b05f to your computer and use it in GitHub Desktop.
Save satyx/aaba695a760bd6f066ffc9177b11b05f to your computer and use it in GitHub Desktop.

Pointers to Functions and Member Functions : Something you shouldn’t have seen-zoned

Introduction

Probably one of the biggest advantages as well as biggest source of vulnerability in C/C++ are pointers (if handled carelessly, of course). Unfortunately, this is usually aliased with the thought “used to access a variable indirectly i.e. access variable’s memory location” by the beginners, and misses (or seen-zones :P) out an entire dimension of dereferencing functions using pointers. In this blog, I will try to demonstrate magnificence of function pointers

Pointer to Functions

To start with, let's have a basic clarity, pointers can point to any virtual memory location (it’s accessibility is a different story, depending upon the type of memory). So crudely speaking, functions themselves are a set of instructions stored in a memory region which is accessed using a function identifier. Essentially the execution jumps to instructions stored at a different location (or should I say, the instruction pointer gets updated with the address of the first instruction of the function, under the hood). How this is done is altogether a different and interesting story, but what is important to note here is since execution jumps from instruction at one memory location to another using its identifier (also called “calling the function), the type of the identifier has to be a pointer (as it is storing a memory location). Thus we knowingly or unknowingly have been using a pointer to a function since day 0.

Consider the code snippet:

 int foo(int x, float y)
 {
     //action
 }

Thus here, &foo will give the memory address of the function where it is stored(or better say where it’s first instruction is stored). We can declare pointers to functions as:

int (*funcPtr)(int, float); 

This can reference any function whose definition involves a return type as int and two arguments of type int and float. If there are no arguments, you can keep parentheses empty.

funcPtr = &foo;	// since foo matches with the type

Ugly, isn’t it? Indeed (A clean way is discussed at the end), but that’s for a reason. Using parentheses around *funcPtr is important as * and () operator have same precedence and would thus follow right to left associativity and so compiler would actually read it as int* (funcPtr(int, float)) i.e., a function named funcPtr that takes two arguments of type int and float and returns a pointer to an integer. Makes sense, right?

To make a constant pointer to function we can use:

int (*const funcPtr)(int,float) = &foo;

So, extrapolating this, if we want to call a function with default arguments, can we put them inside the parentheses? Minor heartbreak but you can’t. To understand this, one needs to recall how default parameters are dealt by compilers. Essentially they replace argument list with the provided default values during compile time whereas resolution of function pointers occurs at runtime.

Long story short, NO YOU CAN’T USE DEFAULT ARGUMENTS WITH FUNCTION POINTERS and you have to deliberately pass those default values as a part of the argument list.

void bar(int x=3)
{
    //action
}

int main()
{
    ...
    void (*funcPtr)(int) = &bar;
    (*funcPtr)(3;			 // calling
    ...
}

Uses

Okay, so why so much drama about all this? Well, one of the best advantages of this is passing functions as arguments, which thus can be used for various purposes including callbacks just like in javascript. Thus with this, you can write generic functions rather than writing different versions of a function having same functionality

Note: Just like other arguments, you can have default value for arguments of type function pointer

Another interesting use case would be, using an input string to call a function with the name identical to the user input. All you need to do is Map string with appropriate pointer to function and use user input string as key to access respective function)

Pointers to Member Functions

Whatever! That’s how we're gonna treat every function with a function pointer? Ummm theoretically yes, practically no :P . What the heck? Well there’s a small twist when it comes to non-static member functions. They are a bit different as compared to normal functions. Though you directly call non-static member functions with the help of objects, a hidden argument is secretly passed i.e. a pointer to the object of the class type (which you access as this inside any non-static member function). Hence the type of non-static member functions are different compared to that of normal function

For a function, int func(int) the type is int (*)(int) , if it’s a normal function or a static member function of a class int(Delta::*)(int), if it’s a member function of class Delta

Consider code snippets to get a better understanding:

class A
{
  int x;
public:
  A():x(0){}
  static int func(int x)
  {
    return x*2;
  }
  void setter(int val)
  {
    x = val;
  }
  int getter() const
  {
    return x;
  }
};

int main()
{
  A obj;

  void (A::*fptr1)(int) = &A::setter;	//non-static member function
  int (A::*fptr2)() const = &A::getter;	//non-static constant member function
  int (*fptr3)(int) = &A::func;		//static member function

  (obj.*fptr1)(3);

  std::cout<<(obj.*fptr2)()<<std::endl;
  std::cout<<(*fptr3)(5)<<std::endl;

  return 0;
}

Type Aliasing

Finally, as promised in the beginning, let me share the trick to get around this ugly syntax, all hail the type aliasing!

using func_t= int(*)(int,float);
…
func_t f = &foo;

With type aliasing, we can use func_t to declare any such function pointer of type int(*)(int,float).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment