Function Pointers in Structs

Started by
6 comments, last by Aardvajk 10 years, 1 month ago

I'm developing a thing in C, and currently I have followed a style where I provide a struct declaration containing data, and separate functions for acting upon said data. Something like this:

MyData.h


struct MyData
{
   int a;
   float b;
};

void initialise( struct MyData* instance );
int get_a( struct MyData* instance );
float get_b( struct MyData* instance );

MyData.c


#include <MyData.h>

void initialise( struct MyData* instance )
{
    instance->a = 2;
    instance->b = 7.3f;
}

int get_a( struct MyData* instance )
{
    return instance->a;
}

float get_b( struct MyData* instance )
{
    return instance->b;
}

main.c


#include <MyData.h>

int main( void )

{

    MyData test;
    initialise( &test );
    int x = get_a( &test );
    float y = get_b( &test );

    return 0;

}

I'm wondering whether or not to do some pointer trickery to make it look more OO, like the following:

MyData.h


struct MyData
{
    int a;
    float b;

    int (*get_a) (struct MyData*);
    float (*get_b) (struct MyData*);
};

void initialise( struct MyData* instance );

MyData.c



#include <MyData.h>

static int get_a( struct MyData* instance )
{
    return instance->a;
}

static float get_b( struct MyData* instance )
{
    return instance->b;
}

void initialise( struct MyData* instance )
{
    instance->get_a = get_a;
    instance->get_b = get_b;

    instance->a = 2;
    instance->b = 7.3f;
}

main.c



#include <MyData.h>

int main( void )

{

    MyData test;
    initialise( &test );
    int x = test.get_a( &test );
    float y = test.get_b( &test );

    return 0;

}

The benefit I see is being make get_a and get_b static, so they can't be called directly without having instantiated a type from MyData.

What other pro's and con's are there from doing this, and am I doing the initialisation correctly for assigning the function pointers?

"I would try to find halo source code by bungie best fps engine ever created, u see why call of duty loses speed due to its detail." -- GettingNifty
Advertisement

The first approach is how normal member functions work in C++

The second is similar to how virtual functions work.

The pros and cons are roughly the same...you get some overhead in calling function pointers, but it can open the door to polymorphic "classes".

I can agree it looks a bit nicer, but that might be because I'm a C++ guy, and the question arises, why not just use C++? smile.png


The benefit I see is being make get_a and get_b static, so they can't be called directly without having instantiated a type from MyData.

Not sure this is a big benefit though...

You'll just have a different runtime error while calling the functions if the struct isn't correctly initialized, though I guess it might fail faster..

And wouldn't you need a pointer to the struct anyway to be able to call the function? Hopefully it would be initialized at that point if your architecture is good.

Some obvious drawback are:

  1. The object has to physically contain a pointer for every "member function" you want to use.
  2. The syntax is worse in the sense that you have to provide the object twice: once to obtain the function pointer and once to pass it to the function. In C++, for example, the hidden this-parameter is implied from the syntax you use to call the member function on and you only mention the object once.
  3. No way to force the two objects to be the same. Nothing stops you from doing test1.get_a(&test2).

Don't fool yourself that it's more OO because you have an object on the left hand side of a period and a function on the right hand side; OO is just as fine with the object as a parameter instead. It's about how you design your objects and functions to operate on them, not about what syntax you use to call a function.

One benefit of storing a function pointer is to get runtime-dynamic behavior. For example, as Olof said, it is a similar approach to how virtual functions are typically implemented. For compile-time behavior, there's no reason to store the function pointers.

I've done this in C to get a poor-man's polymorphism. The syntax is awkward but its a reasonable way to implement an interface you can pass around to methods that don't know which actual concrete functions will be used. Just makes me wish I was using C++ though - any reason you aren't?

If the methods are always going to be the same, its pointlessly over-complex compared to free functions taking the a pointer to the struct as a member, as Brother Bob points out, and wastes unnecessary space. I don't really buy your argument about protecting you from calling the methods accidentally. If you need that kind of encapsulation protection, you're using the wrong language. There are a million other ways you can make similar mistakes in C so this doesn't buy you a great deal, certainly not enough to warrant the overhead in terms of memory and syntax.

But as a way to implement runtime polymorphic interfaces in C it is okay. You could just simulate a v-table pointer to a static table for each "derived type" as well though to avoid having to store all the function pointers in every instance, although the syntax gets even worse then of course.

But making things "look OO" is rarely a good motivation in itself.

As I see it, if you decide to use C you should do it only if you want to avoid being tempted as easily to create overcomplicated OOP-isms. If you want OOP you can spare yourself from emulating it and just switch the compiler to C++ and be able to use all those goodies like function overloading, namespaces, destructors, std library, exceptions and so on.

The example looks like its doing no useful work (I guess its cause of being a quick example only); theres no need for useless getters with function pointers for such a simple struct.

In C I think a functional style without structs would make more sense:


float calculate_whatever(int a, float b);

Or with the struct:


typedef struct {
  int a;
  float b;
} MyData;

int main() {
  MyData data={.a=1, .b=3.141f};

  float f=mydata_calculate(&data);
  // ...
}

Though if you plan on allocating many of those structs think about using SoA style and providing functions for looping over whole separate arrays.

@all - Thanks a lot, the information provided was very insightful to me, and I see that applying the "OO" style to my program is going to be overkill, adding unnecessary complexity.


Just makes me wish I was using C++ though - any reason you aren't?

Three reasons: Mainly because the program in question is simple, needs to be very close to the underlying hardware, and there actually isn't a compiler (that I know of) that is capable of compiling C++ code to the target architecture.

Even if there were a possibility to use C++, I'd neglect to do so. I feel more comfortable programming micro controllers in C because it keeps things clean and simple. Especially when others work with your code.

"I would try to find halo source code by bungie best fps engine ever created, u see why call of duty loses speed due to its detail." -- GettingNifty

So, I assume you're providing accessor functions (get_a(), get_b()) to ensure the data is only accessed by those functions, but the way you've implemented it, anyone can just access it via myData->a, etc.

If you want to hide the data, you can externally define the structure as an empty array in a header file you publish:

// myDataPublic.h
typedef struct
{
  unsigned char Data[8];
} MyData;
 
int MyDataGetA(MyData* data);
float MyDataGetB(MyData *data);

So whoever is using it doesn't know the contents of the struct.

Considering you're working on a small micro, processing/memory may be an issue, and trying to get fancy with accessors is a waste IMO. Just allow them to access the members directly.

My Gamedev Journal: 2D Game Making, the Easy Way

---(Old Blog, still has good info): 2dGameMaking
-----
"No one ever posts on that message board; it's too crowded." - Yoga Berra (sorta)

I think the best way to implement data hiding in C is to have a unit provide handles and manage the memory for the actual data privately in the unit.

So you have like StringHandle getString(const char*) and FreeString(StringHandle) etc.

This is the only way I know to guarantee in C that data and implementation remain private and means you can centralize control of allocation.

But if you need thus kind of control you need a very good reason to be using C in the first place.

This topic is closed to new replies.

Advertisement