Game programming/Function pointers

From Wikiversity
Jump to navigation Jump to search

In C, pointers are everywhere. You are probably familiar with pointers to variables. Maybe even pointers to structs. A pointer is a variable which holds a memory address. It usually has some useful value, like a acting as a reference to a variable in a different scope. In C, you can also have pointers to functions.

Requirements[edit | edit source]

  • C knowledge

Objectives[edit | edit source]

  • To know how to make a pointer to a function
  • To know how to use a pointer to a function
  • To get to know some usage examples of pointers to functions

Introduction[edit | edit source]

First, let's look at a function declaration.

    double getAvg(double a, double b)
    {
        return (a + b) / 2;
    }

As you can see, we have here a function called "getAvg". It receives two doubles and returns a double.

Explanation[edit | edit source]

A pointer to a function does looks mostly not different from a regular function, except it has the '*' symbol as in all pointers, and parenthesis as follows:

    /* Declaration */
    double (*fp(double, double)); // Or double (*fp)(double, double); or just double (*fp)();
    
    /* Assignment */
    fp = &getAvg;
    
    /* Use */
    fp(2, 3); // We can also use (*fp)(2, 3); to be more explicit about the fact that fp is a pointer - but it is a case of personal preference.

Here we have a pointer called "fp", so we'll refer to it as such. It is a pointer to a function that receives two doubles and returns a double. Note how we do not add the parenthesis after getAvg when we assign it's address to fp. Also note the '&' operator. Using a pointer to a function is not different from using a normal function. Hence, we could do the following:

    printf("fp(2, 3) = %f.2\n", fp(2, 3));

Application[edit | edit source]

Pointers to functions serve three general purposes. One is that they provide a way to have methods in structs, the second is that they allow for some degree of polymorphism, and the third is passing functions to functions.

To create methods in a struct,

    #include <stdio.h>
    
    typedef struct
    {
        double a;
        double b;
        
        double (*getAvg)(MyStruct_t*);
    } MyStruct_t;
    
    double _MyStruct_t_getAvg(MyStruct_t* this)
    {
        return (this->a + this->b) / 2;
    }
    
    MyStruct_t* createMyStruct(double da, double db)
    {
        MyStruct_t* new = (MyStruct_t*) malloc(sizeof(MyStruct_t));
        
        new->a = da;
        new->b = db;
        new->getAvg = &_MyStruct_t_getAvg;
        
        return new;
    }
    
    int main(int argc, char* argv[])
    {
        MyStruct_t* strct = createMyStruct(2, 3);
        printf("getAvg() = %f.2", strct->getAvg(strct));
        
        return 0;
    }

Note: This example isn't that useful. It requires work. Note: Unless each struct has a possibly different pointer (ie. you aren't just trying to make OOP), then you are wasting space! Just because you can make OOP work in C doesn't mean you should - or even that it's good to do so.

As an example of polymorphism,

    #include <stdio.h>
    
    void doOne(void)
    {
        printf("I've done One\n");
    }
    
    void doTwo(void)
    {
        printf("I've done Two\n");
    }
    
    void doNothing(void)
    {
        printf("I've done nothing\n");
    }
    
    int main(int argc, char* argv[])
    {
        void (*work)(void) = doNothing;
        int state;
        
        for(state = 0; state < 2; ++state)
        {
            if(state == 0)
            {
                work = &doOne;
            }
            else if(state == 1)
            {
                work = &doTwo;
            }
            else
            {
                work = &doNothing;
            }
            
            printf("State %d,\n", state);
            work();
        }
        
        printf("Last work:\n");
        work();
        
        return 0;
    }

Note: you can create an array of function pointers void (*function_pointers[]) to allow cycling through functions if strictly necessary. Just remember, you the correct expected return operand when cycling through - so if you're switching some pointer through these values, they'd better all have at least the same return size, and preferably same return type.

And an example of sending functions through functions,

    #include <stdio.h>
    
    void doWork(void (*beforeBegin)(void), void (*afterDone)(void))
    {
        beforeBegin();
        
        printf("I've done some work.\n");
        
        afterDone();
    }
    
    void logBegin(void)
    {
        printf("LOG: I'm about to do some work.\n");
    }
    
    void logEnd(void)
    {
        printf("LOG: I'm done doing some work.\n");
    }
    
    int main(int argc, char* argv[])
    {
        doWork(logBegin, logEnd);
        
        return 0;
    }