Here’s another installment on Object Oriented C programming. This time, we’re going to take a look at how inheritance allows you to simulate a generic function courtesy of the C preprocessor. If you have ever wondered how those spiffy objects in other programming languages always get “printed,” here’s one way to do it.
First, let’s write a little code:
#include < stdio .h>
typedef struct _object Object;
#ifndef OOCP_PRINTFUNC
typedef int (*PrintFunc) (void * stream,
const char * format,
...);
#define OOCP_PRINTFUNC
#endif
#ifndef OOCP_PRINTER
typedef void (*Printer) (Object * o,
PrintFunc print_func,
void * stream);
#define OOCP_PRINTER
#endif
#define object_print(o,p,s) ((Object*)o)->print ((Object*)o,(PrintFunc)p,s);
struct _object {
Printer print;
};
typedef struct _thing {
Object o;
int value;
} Thing;
void
thing_printer(Object * o, PrintFunc print, void * stream) {
Thing * t = (Thing *)o;
print(stream, "Value from thing_printer: %dn", t->value);
}
int
main(int atgc, char ** argv) {
Thing t;
t.o.print = thing_printer;
t.value = 1;
object_print(&t, fprintf, stdout);
return 0;
}
Ok, some 50 lines of code. Doesn’t look like much, but if you’re new to C, and haven’t been following along, rest assured: we’re starting to flex some muscle now.
That is, there’s a lot going on in this code.
Let’s start at the top:
typedef struct _object Object;
#ifndef OOCP_PRINTFUNC
typedef int (*PrintFunc) (void * stream,
const char * format,
...);
#define OOCP_PRINTFUNC
#endif
- Line 3 is familiar, but we would normally expect this in a header file rather than a source file. We’re keeping the
typedefhere to keep the listing simple. - But what’s this
typedefon Line 6? And why these ugly #defines on lines 5, 9 and 10?
Easy stuff first. The #defines protect your code from a compiler redefinition error. The idea is you want to define something in one place only, and have that thing (whatever it is) defined throughout your program. Remember, we’re dealing with strong typing, and a compiled language. Interpreted languages, Ruby or PHP for example, have little limitation on how your data may be defined.
In those languages, if you want to declare something a function in one line of code, then that same thing as an array in another line of code, that’s your business.
But not with C.
Line 6 gets into the meat. Recall we typedef’ed structs in header files to create hidden implementations. The type checker just needs the declaration to compile properly, the implementation can be anywhere within the typedef’s scope. Here, we’re using typedef to create a pointer to type of function, which is called PrintFunc.
If you think the prototype looks familiar, perhap similar to functions defined in stdio.h, you’re thinking correctly.
Let’s continue…
#ifndef OOCP_PRINTER
typedef void (*Printer) (Object * o,
PrintFunc print_func,
void * stream);
#define OOCP_PRINTER
#endif
#define object_print(o,p,s) ((Object*)o)->print ((Object*)o,(PrintFunc)p,s);
struct _object {
Printer print;
};
With Line 13, we’re getting a bit more esoteric. Now we’re building our typedef’ed function from our own derived types instead of C’s primitives, or from types defined in the standard library. We’ve seen this stuff before, though, even if just a few lines before in the case of PrintFunc.
Now, any function which prints and takes the same arguments as Printer can be assigned to a Printer variable. More importantly, such functions can be assigned to member variables in any struct declaring a Printer member.
But line 19 is whackadoodle.
It’s a macro. Not a function.
Recall in Java you have Object.tostring? This is pretty close to that.
We now have all the tools we need, so let’s put it together.
typedef struct _thing {
Object o;
int value;
} Thing;
void
thing_printer(Object * o, PrintFunc print, void * stream) {
Thing * t = (Thing *)o;
print(stream, "Value from thing_printer: %dn", t->value);
}
We’ve got our Thing (Line 25), and a way to print (Line 31) our Thing. Life is good.
Now let’s use our Thing:
int
main(int atgc, char ** argv) {
Thing t;
t.o.print = thing_printer;
t.value = 1;
object_print(&t, fprintf, stdout);
return 0;
}
And there you have it, on Line 44: print that Thing out using it’s very own print “method.”
Exercise: Instead of declaring a Thing t, declare a Thing * t. Allocate its memory, print it, then remember to deallocate the memory afterwards. Run the program through valgrind to check your memory usage. You should have some memory left on the heap at the end of runtime, but nothing left over (no leaks).
The usual caveats…
You won’t want to use object_print for everything, but you might find that that a printing function defined in the same way works in a deeper class hierarchy. Suppose you have a Matrix class, which is the child of Object, and has children representing different kinds of matrices. In this case, having a matrix_print might be just the ticket.
Remember: object oriented anything is not a silver bullet for slaying design dragons. The simple framework we’re developing here is just that, simple. Instead of trying to bend your problem into this framework, use these concepts to create a framework that works best for your project.
Here’s some related material on function pointers:
- The Function Pointer Tutorials: An excellent followup by Lars Haendel going into considerably more detail than what’s written here.
- Declaring, Assigning, and Using Function Pointers: This is a classic piece of exposition on C, written by Steve Summit. I recall Steve from way back in Usenet days, before the World Wide Web. He’s the real deal and it’s worth studying this article in detail.
Stay tuned for an article specifically on how to typedef function pointers.