All Rights reserved by Zack Smith.
@http://home.comcast.net/~fbui/OOC.html

Revision 0.13
Copyright © 2008-2010 by Zack Smith
All rights reserved, except for array_template.h which is covered by the GPL.
Introduction
Object-oriented programming languages (OOPLs) became dominant years ago over older procedural languages like C, Pascal and the like. With these languages come fundamental problems and complexities that are not necessarily desired by a software architect.

The case of C++ exemplifies the pitfalls of using OO languages: overuse of its templates and multiple inheritance make software design, implementation, and debugging more difficult than it needs to be. While some C++ code is well-written, some C++ code is an illegible tangle of derived classes worse than any “goto” based problem from the distant past. And yet, nevertheless a typical C++ partisan will defend such a C++ tangle as being perfectly valid and appropriate.

Due to the problems of common object-oriented languages, it is sometimes preferable for a programmer to use a non-OOP language such as C augmented to use object-oriented practices.
Approach A: Struct with pointers
A simple technique for implementing an object in C is to imitate C++, and provide a struct with pointers to methods inside.

typedef struct obj {
int a;
char *b;

// Class methods
struct obj (*new)();
void (*delete)(struct obj *);

// Object methods
void (*set_a)(struct obj*,int);
void (*set_b)(struct obj*,char *);
} Object;

In use:

Object *obj = class_struct->new();
obj->set_a (obj, 123);

Or a more memory-efficient approac would be to put the function pointers in a table that’s common to the class, thus:

typedef struct {
// Class methods
struct obj (*new)();
void (*delete)(struct obj *);

// Object methods
void (*set_a)(struct obj*,int);
void (*set_b)(struct obj*,char *);
} ObjectMethods;

typedef struct obj {
int a;
char *b;

ObjectMethods* methods;
} Object;

In use:

Object *obj = class_struct->new();
obj->methods->set_a (obj, 123);

A possible pitfall of this approach is that methods in the struct might be NULL due to lack of proper initialization or may have been erroneously overwritten. In either case resulting in a program crash would result unless a dedicated “method caller” routine were used to checked for NULLs and unlikely pointer values. And if a function pointer is overwritten, there will be no easy way to ascertain where the pointer was overwritten.
Approach B: Struct and message-passer
A better alternative is to use an approach similar to Objective C’s, and pass messages to objects based on a method name.

typedef struct obj {
int class_id;
int a;
char *b;
} Object;

A send_message function is used to send messages to objects and is implemented to take variable numbers of arguments. The class initializer is responsible for loading all of its methods into a hash of functions. Note that it’s necessary to include “stdarg.h”.

int send_message(int count, …)
{
int tmp;
va_list ap;
va_start(ap, count);
Object *this = va_arg(ap, Object*);
char *msg = va_arg(ap, char*);

// Here, look up method in hash and call appropriate function…

va_end(ap);
}

In use:

Object *obj;
obj = send_message (class_struct, “new”);
send_message (obj, “set_a”, 123);

Of course, in practice “send_message” would be shortened to something more useable, e.g. “S”. In Objective C, one simply encloses the message passing in brackets e.g.

[ object message: parameters ];

An advantage to this approach is that method pointers are less likely to be overwritten since they are not in the data struct.

This approach can also be extended to include the hashing of object data, to provide further protection against program errors.

A further refinement might be to include some non-textual predefined numeric method names, e.g. implemented using enum or #define, which index into a table to avoid doing any string comparison. For instance:

enum {
MESSAGE_NEW=1,
MESSAGE_DESTROY=2,
MESSAGE_DUMP=3
};
Object *obj = send_message (rectangle_class_struct, MESSAGE_NEW);
send_message (obj, “set_width”, 456);

Templates in C
Templates have always been one of the heavily promoted selling points of C++. The claim goes that you cannot create templates in C therefore C is bad. If you can’t do templates then you’re lost in the wilderness of having to create a separate List for int, float, char*, bool, et cetera.

Alas, it’s an empty claim. In reality you can create templates in C without too much trouble if you want to.
Approach A: Putting the implementation into a macro
This subsection has been superceded by my page dedicated to this topic: Templates in C.

I like this approach for two reasons:

It is in accord with common C usage.
It forces you to keep the data type small since it is rather painful to write a long data type.

Here’s my example of an Array type:

// array_template.h:
// This code is copyright (C) 2009 by Zack Smith.
// This .h file is covered by the GNU Public License version 2.

#define ARRAY_TEMPLATE(AAA) \
typedef struct { \
int size; \
AAA *objects; \
} Array_##AAA ; \
Array_##AAA *Array_##AAA##_new (int size) { \
Array_##AAA *nu = (Array_##AAA *)malloc(sizeof(Array_##AAA)); \
nu->size = size; \
nu->objects = (AAA *)malloc(size*sizeof(AAA)); \
return nu; \
}; \
void Array_##AAA##_delete (Array_##AAA *ary) { \
if (ary) { \
if (ary->objects) \
free (ary->objects); \
free (ary); \
} \
}; \
AAA Array_##AAA##_get(Array_##AAA *ary, int index) { \
if (!ary) { \
return 0; \
} \
if (index = ary->size) { \
return 0; \
} \
return ary->objects[index]; \
} \
void Array_##AAA##_set(Array_##AAA *ary, int index, AAA value) { \
if (!ary || !value) { \
return; \
} \
if (index = ary->size) { \
return; \
} \
ary->objects[index] = value; \
}

Notice my use of the C preprocessor’s standard ## notation for concatenating a #define symbol with other text.

This approach puts the typedef and implementation in the C file, but in theory one could rewrite array_template.h to permit the generation of separate .c and .h files for templated types.

At any rate, here’s a simple example of how to use an Array of int:

#include
#include “array_template.h”

ARRAY_TEMPLATE(int) // Define code that we need.

int
main()
{
Array_int *a;
a = Array_int_new (100);
Array_int_set (a, 33, 6);
printf (“a[33] is %d\n”, Array_int_get (a, 33));
Array_int_set (a, 33, 5);
printf (“a[33] is now %d\n”, Array_int_get (a, 33));
Array_int_delete (a);
}

There is one limitation to this approach. You cannot specify a type that has a “*” or brackets in it. But you can always typedef that. For instance:

#include
#include “array_template.h”

typedef char* string;
ARRAY_TEMPLATE(string) // Define code that we need.

int
main()
{
Array_string *a;
a = Array_string_new (100);
Array_string_set (a, 33, “this is a test”);
printf (“a[33] is ‘%s’\n”, Array_string_get (a, 33));
Array_string_set (a, 33, “2nd test”);
printf (“a[33] is now ‘%s’\n”, Array_string_get (a, 33));
Array_string_delete (a);
}

The output of this is:

$ ./a
a[33] is ‘this is a test’
a[33] is now ‘2nd test’

Approach B: Inclusion of a .c file
This second approach is not one I like but you see it mentioned in Usenet forums. You write a C file for a data type, let’s say a list. But everywhere that data type’s name should be you insert MYTYPE or a similar #define. Then you do the following, using the example of MYTYPE being “int”.

#define MYTYPE int
#include “array_template.c”
List_int *myList;

Download
I’m no longer offering OOC or C template downloads since my focus has shifted to creating my C@ language and compiler.

Advertisements